diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index c748bf1..eae9b41 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -7,7 +7,7 @@ Autorski system CMS z panelem administracyjnym (17 modułów admin, 13 modułów Autorski system CMS umożliwiający zarządzanie treściami i stronami internetowymi. ## Already Completed -- Domain (10 repos): Articles, Languages, Layouts, Pages, Settings, User, Scontainers, Banners, Authors, Newsletter +- Domain (13 repos): Articles, Languages, Layouts, Pages, Settings, User, Scontainers, Banners, Authors, Newsletter, SeoAdditional, Cron, Releases+Update - Shared (7 modules): Cache, Helpers, Html, Image, Tpl, Email, Security - Form Edit System: FormEditViewModel, multi-tab, validation, persistence - PHPUnit base: Bootstrap, 3 test files @@ -17,7 +17,7 @@ Autorski system CMS umożliwiający zarządzanie treściami i stronami interneto ### Must Have - Centralny PSR-4 autoloader (hybrydowy z legacy) -- Wszystkie Domain repositories (Scontainers, Banners, Authors, Newsletter, SEO, Cron, Releases) +- ✓ Wszystkie Domain repositories — Phase 5 complete - Shared\Email + Shared\Security (CsrfToken, HMAC-SHA256) - Admin\ namespace z DI dla wszystkich 17 modułów - Frontend\ namespace dla wszystkich front modułów @@ -46,4 +46,4 @@ Autorski system CMS umożliwiający zarządzanie treściami i stronami interneto --- *Created: 2026-04-04* -*Last updated: 2026-04-04 after Phase 4* +*Last updated: 2026-04-26 after Phase 5* diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 7bc154d..b4d092f 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -6,7 +6,7 @@ Pełna refaktoryzacja cmsPRO do architektury DDD wzorowanej na shopPRO. Wzorzec: ## Current Milestone **v0.1 Refaktoryzacja** (v0.1.0) Status: In progress -Phases: 4 of 19 complete +Phases: 5 of 19 complete ## Already Completed (before PAUL) - **Domain (6 repos):** Articles, Languages, Layouts, Pages, Settings, User @@ -22,7 +22,8 @@ Phases: 4 of 19 complete | 2 | Shared: Email + Security | 1 | Complete | 2026-04-04 | | 3 | Domain: Scontainers + Banners | 1 | Complete | 2026-04-04 | | 4 | Domain: Authors + Newsletter | 1 | Complete | 2026-04-04 | -| 5 | Domain: SeoAdditional + Cron + Releases | 1 | Not started | - | +| 04h | **HOTFIX:** HTTPS update endpoint (out-of-roadmap) | 1 | Complete | 2026-04-26 | +| 5 | Domain: SeoAdditional + Cron + Releases | 1 | Complete | 2026-04-26 | | 6 | Admin: Base Infrastructure | 1 | Not started | - | | 7 | Admin: Articles + ArticlesArchive | 1 | Not started | - | | 8 | Admin: Pages + Layouts | 1 | Not started | - | diff --git a/.paul/STATE.md b/.paul/STATE.md index 87fa9d1..bc40f1e 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -2,21 +2,21 @@ ## Project Reference -See: .paul/PROJECT.md (updated 2026-04-04) +See: .paul/PROJECT.md (updated 2026-04-26) **Core value:** Autorski system CMS umożliwiający zarządzanie treściami i stronami internetowymi. -**Current focus:** Phase 4 complete — ready for Phase 5 +**Current focus:** Phase 5 complete — ready for Phase 6 (Admin: Base Infrastructure) ## Current Position Milestone: v0.1 Refaktoryzacja -Phase: 4 of 19 (Domain: Authors + Newsletter) — Complete -Plan: 04-01 complete -Status: Loop closed, ready for next PLAN -Last activity: 2026-04-04 — Phase 4 complete, UNIFY done +Phase: 6 (Admin: Base Infrastructure) — Not started +Plan: Not started +Status: Ready to plan Phase 6 +Last activity: 2026-04-26 — Phase 5 complete, transitioned to Phase 6 Progress: -- Milestone: [▓▓░░░░░░░░] 20% +- Milestone: [▓▓▓░░░░░░░] 26% (5 of 19 phases) ## Loop Position @@ -29,8 +29,8 @@ PLAN ──▶ APPLY ──▶ UNIFY ## Performance Metrics **Velocity:** -- Total plans completed: 4 -- Total execution time: ~22min +- Total plans completed: 5 +- Total execution time: ~27min **By Phase:** @@ -40,17 +40,22 @@ PLAN ──▶ APPLY ──▶ UNIFY | 02-shared-email-security | 1/1 | ~8min | ~8min | | 03-domain-scontainers-banners | 1/1 | ~2min | ~2min | | 04-domain-authors-newsletter | 1/1 | ~2min | ~2min | +| 04h-hotfix-https-updates | 1/1 | ~90min | ~90min | +| 05-domain-seoadditional-cron-releases | 1/1 | ~5min | ~5min | ## Accumulated Context ### Decisions +- 2026-04-26: Phase 5 — UpdateRepository przyjmuje ($db, $settings) w konstruktorze — settings potrzebny do update_key i wersji. +- 2026-04-26: Phase 5 — Cron helper methods (get_site_meta_*) stały się private w CronRepository — były wywoływane tylko wewnętrznie. +- 2026-04-26: Phase 5 — class.Cron.php zachowuje brak namespace (klasa globalna) — cron.php używa bezpośrednio. +- 2026-04-26: Hotfix 04h — full-patch wszystkich 121 paczek (zamiast minimal-patch). Powód: paczki nadpisują class.S.php w różnych wersjach, częściowy patch ryzykuje regresję podczas chain-update. - Centralny autoloader zamiast duplikatów - CsrfToken: single token per session (shopPRO pattern) - Email: PHPMailer require via __DIR__ absolute paths - Shared layer kompletny: Cache, Helpers, Html, Image, Tpl, Email, Security - Wrapper delegation: factory creates new repo per call (no singleton) - Front repos: $lang[0] passed explicitly, repos don't use globals -- Front caching: migrated from \Cache:: to \Shared\Cache\CacheHandler:: - Newsletter: globals ($settings, $lang) passed as explicit params to repo methods ### Deferred Issues @@ -61,10 +66,10 @@ None. ## Session Continuity -Last session: 2026-04-04 -Stopped at: Phase 4 complete, loop closed -Next action: Run /paul:plan for Phase 5 (Domain: SeoAdditional + Cron + Releases) -Resume file: .paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md +Last session: 2026-04-26 +Stopped at: Phase 5 complete, loop closed +Next action: /paul:plan dla Phase 6 (Admin: Base Infrastructure) +Resume file: .paul/ROADMAP.md --- *STATE.md — Updated after every significant action* diff --git a/.paul/changelog/2026-04-26.md b/.paul/changelog/2026-04-26.md new file mode 100644 index 0000000..0d3c74f --- /dev/null +++ b/.paul/changelog/2026-04-26.md @@ -0,0 +1,52 @@ +# 2026-04-26 + +## Co zrobiono + +- [Phase 04h, Plan 01] Hotfix HTTPS update endpoint: naprawa zablokowanego mechanizmu aktualizacji we wszystkich instancjach cmsPRO +- Patch http://www.cmspro.project-dc.pl -> https:// w kodzie zrodlowym (Helpers.php, factory/Update.php) i instancji testowej +- Audit 542 paczek aktualizacji - wykryto 121 z buggy http:// URL +- Patch 121 paczek (autoload/class.S.php / Helpers.php / factory/Update.php) z http -> https +- Patch cmsPro.zip (base install) z http -> https +- Wstrzykniecie kotwicy fixa do ver_1.519.zip (oryginalnie tylko class.Articles.php; dodano patched class.S.php + factory/Update.php) - SHA256: 14e5754c75884fcc... +- Odkrycie bug-a #2 podczas UAT: klucz licencji z `#` lamie URL przez fragment delimiter -> serwer dostaje pusty klucz -> brak nowych wersji +- Patch urlencode($settings['update_key']) w kodzie zrodlowym + 64 paczkach + kotwicy +- Generacja upload-checklist.md (124 pliki: cmsPro.zip + 121 ZIP + 2 manifest) +- Auto-deploy ftp-kr.json przeniosl pliki na serwer cmspro.project-dc.pl +- UAT confirmation: instancja testowa widzi i instaluje aktualizacje > 1.519 +- Cleanup 1085 plikow .bak / .preurlencode.bak / .preanchor.bak (lokalnie + FTP) przez .NET FtpWebRequest + +- [Phase 05, Plan 01] Domain layer kompletny: SeoAdditional + Cron + Releases + Update repositories +- Utworzono Domain\SeoAdditional\SeoAdditionalRepository (elementDelete, elementSave, elementDetails) +- Utworzono Domain\Cron\CronRepository (3 pub + 12 private helper methods, crawling stron) +- Utworzono Domain\Releases\ReleasesRepository (9 metod: wersje, licencje, discover) +- Utworzono Domain\Releases\UpdateRepository (auto-update mechanizm, przyjmuje $db + $settings) +- Zaktualizowano 4 legacy wrappery: class.SeoAdditional, class.Cron, class.Releases, class.Update + +## Zmienione pliki + +- `autoload/Shared/Helpers/Helpers.php` +- `autoload/admin/factory/class.Update.php` +- `updates/cmsPro.zip` +- `updates/**/ver_*.zip` (121 paczek) +- `updates/**/ver_*_manifest.json` (2 manifesty) +- `.paul/phases/04h-hotfix-https-updates/04h-01-PLAN.md` +- `.paul/phases/04h-hotfix-https-updates/04h-01-SUMMARY.md` +- `.paul/phases/04h-hotfix-https-updates/audit-report.md` +- `.paul/phases/04h-hotfix-https-updates/patch-log.md` +- `.paul/phases/04h-hotfix-https-updates/patch-urlencode-log.md` +- `.paul/phases/04h-hotfix-https-updates/upload-checklist.md` +- `.paul/phases/04h-hotfix-https-updates/scripts/audit-packages.ps1` +- `.paul/phases/04h-hotfix-https-updates/scripts/patch-packages.ps1` +- `.paul/phases/04h-hotfix-https-updates/scripts/patch-urlencode.ps1` +- `.paul/phases/04h-hotfix-https-updates/scripts/inject-anchor-1519.ps1` +- `.paul/phases/04h-hotfix-https-updates/scripts/cleanup-baks.ps1` +- `autoload/Domain/SeoAdditional/SeoAdditionalRepository.php` +- `autoload/Domain/Cron/CronRepository.php` +- `autoload/Domain/Releases/ReleasesRepository.php` +- `autoload/Domain/Releases/UpdateRepository.php` +- `autoload/admin/factory/class.SeoAdditional.php` +- `autoload/class.Cron.php` +- `autoload/admin/factory/class.Releases.php` +- `autoload/admin/factory/class.Update.php` +- `.paul/STATE.md` +- `.paul/ROADMAP.md` diff --git a/.paul/phases/05-domain-seoadditional-cron-releases/05-01-PLAN.md b/.paul/phases/05-domain-seoadditional-cron-releases/05-01-PLAN.md new file mode 100644 index 0000000..f21e7ee --- /dev/null +++ b/.paul/phases/05-domain-seoadditional-cron-releases/05-01-PLAN.md @@ -0,0 +1,237 @@ +--- +phase: 05-domain-seoadditional-cron-releases +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - autoload/Domain/SeoAdditional/SeoAdditionalRepository.php + - autoload/Domain/Cron/CronRepository.php + - autoload/Domain/Releases/ReleasesRepository.php + - autoload/Domain/Releases/UpdateRepository.php + - autoload/admin/factory/class.SeoAdditional.php + - autoload/class.Cron.php + - autoload/admin/factory/class.Releases.php + - autoload/admin/factory/class.Update.php +autonomous: true +delegation: auto +--- + + +## Goal +Utworzyć Domain repositories dla SeoAdditional, Cron i Releases/Update, oraz zaktualizować legacy klasy do wzorca wrapper delegation. + +## Purpose +Kompletuje Domain layer (wszystkie 13 repozytoriów). Po tej fazie cała logika biznesowa domenowa jest w namespace Domain\ — gotowe pod Admin\ controllers (Fazy 6-13). + +## Output +- autoload/Domain/SeoAdditional/SeoAdditionalRepository.php +- autoload/Domain/Cron/CronRepository.php +- autoload/Domain/Releases/ReleasesRepository.php +- autoload/Domain/Releases/UpdateRepository.php +- Wrappery w 4 legacy klasach (SeoAdditional, Cron, Releases, Update) + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md + +## Prior Work +@.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md + +## Source Files +@autoload/admin/factory/class.SeoAdditional.php +@autoload/class.Cron.php +@autoload/admin/factory/class.Releases.php +@autoload/admin/factory/class.Update.php +@autoload/Domain/Authors/AuthorsRepository.php +@autoload/admin/factory/class.Authors.php + + + + +## AC-1: SeoAdditional Repository +```gherkin +Given klasa admin\factory\SeoAdditional używa global $mdb bezpośrednio +When migrujemy logikę do Domain\SeoAdditional\SeoAdditionalRepository +Then repo przyjmuje $db w konstruktorze, nie używa globals + And factory wrapper deleguje do nowego repo (new repo per call) + And wszystkie 3 metody: elementDelete, elementSave, elementDetails +``` + +## AC-2: Cron Repository +```gherkin +Given legacy class Cron (bez namespace) w autoload/class.Cron.php używa global $mdb +When migrujemy logikę do Domain\Cron\CronRepository +Then repo przyjmuje $db w konstruktorze + And legacy class Cron deleguje do Domain\Cron\CronRepository (new repo per call z global $mdb) + And wszystkie metody zachowane: automaticUpdateSites, getSiteMainLinks, getSiteOtherLinks + metody prywatne helper +``` + +## AC-3: Releases Repository +```gherkin +Given klasa admin\factory\Releases używa global $mdb bezpośrednio +When migrujemy logikę do Domain\Releases\ReleasesRepository +Then repo przyjmuje $db w konstruktorze + And factory wrapper deleguje do nowego repo + And wszystkie metody zachowane: getVersions, promote, demote, discoverVersions, getLicenses, getLicense, saveLicense, deleteLicense, toggleBeta +``` + +## AC-4: Update Repository +```gherkin +Given klasa admin\factory\Update używa global $mdb i $settings bezpośrednio +When migrujemy logikę do Domain\Releases\UpdateRepository +Then repo przyjmuje $db i $settings w konstruktorze + And factory wrapper deleguje do nowego repo (przekazując globals przez konstruktor) + And metoda update() zachowana w pełni +``` + + + + + + + Task 1: SeoAdditional — Domain repo + wrapper + + autoload/Domain/SeoAdditional/SeoAdditionalRepository.php, + autoload/admin/factory/class.SeoAdditional.php + + + Utwórz autoload/Domain/SeoAdditional/SeoAdditionalRepository.php: + - namespace Domain\SeoAdditional; + - konstruktor: __construct($db) — przechowuje $db jako private property + - Metody (camelCase, z logiki class.SeoAdditional.php): + * elementDelete($elementId) — delete z pp_seo_additional + * elementSave($id, $url, $status, $title, $keywords, $description, $text) — insert lub update + \S::delete_cache() + * elementDetails($elementId) — get z pp_seo_additional + - PHP < 8.0: bez typed params, bez named args, bez match + + Zaktualizuj autoload/admin/factory/class.SeoAdditional.php: + - Zastąp każdą metodę wrapperem delegującym: new \Domain\SeoAdditional\SeoAdditionalRepository($mdb)->metoda() + - Pattern z class.Authors.php: global $mdb; $repo = new \Domain\...\Repository($mdb); return $repo->method(...) + - Zachowaj dokładnie te same sygnatury metod (snake_case w factory, camelCase w repo) + + + Grep: Domain\SeoAdditional istnieje w autoload/Domain/SeoAdditional/SeoAdditionalRepository.php + Grep: new \Domain\SeoAdditional\SeoAdditionalRepository istnieje w class.SeoAdditional.php + Brak global $mdb bezpośrednio w repo (tylko w factory wrapper) + + AC-1 satisfied: SeoAdditional repo + wrapper delegation + + + + Task 2: Cron — Domain repo + wrapper + + autoload/Domain/Cron/CronRepository.php, + autoload/class.Cron.php + + + Utwórz autoload/Domain/Cron/CronRepository.php: + - namespace Domain\Cron; + - konstruktor: __construct($db) + - Przenieś CAŁĄ logikę z class.Cron.php do repo jako metody camelCase: + * automaticUpdateSites() — odpowiednik automatic_update_sites() + * getSiteMainLinks() — odpowiednik get_site_main_links() + * getSiteOtherLinks() — odpowiednik get_site_other_links() + * Wszystkie metody prywatne helper (getSiteMetaTitle, getSiteMetaKeywords, itd.) — przenieś jako private methods + - PHP < 8.0: bez typed params + - $mdb zastąp przez $this->db we wszystkich zapytaniach + + Zaktualizuj autoload/class.Cron.php: + - Zachowaj oryginalny namespace (brak namespace — klasa globalna Cron) + - Zastąp każdą public static metodę wrapperem: + global $mdb; $repo = new \Domain\Cron\CronRepository($mdb); return $repo->camelCaseMethod(); + - Usuń ciała helper methods (prywatne) — logika jest teraz w repo + + + Grep: Domain\Cron istnieje w autoload/Domain/Cron/CronRepository.php + Grep: new \Domain\Cron\CronRepository istnieje w autoload/class.Cron.php + Brak bezpośrednich zapytań $mdb-> w class.Cron.php (tylko delegacja) + + AC-2 satisfied: Cron repo + wrapper delegation + + + + Task 3: Releases + Update — Domain repos + wrappers + + autoload/Domain/Releases/ReleasesRepository.php, + autoload/Domain/Releases/UpdateRepository.php, + autoload/admin/factory/class.Releases.php, + autoload/admin/factory/class.Update.php + + + Utwórz autoload/Domain/Releases/ReleasesRepository.php: + - namespace Domain\Releases; + - konstruktor: __construct($db) + - Przenieś logikę z class.Releases.php: getVersions, promote, demote, discoverVersions, getLicenses, getLicense, saveLicense, deleteLicense, toggleBeta + - Prywatna metoda zipDir() jako private helper + - PHP < 8.0: bez ": array", bez ": void", bez ": int", bez ": string" type hints (PHP < 8.0, ale PHP 7.x obsługuje return types — ZACHOWAJ return type hints jeśli były w oryginale, bo PHP 7+ je obsługuje) + - Uwaga: PHP < 8.0 znaczy brak PHP8 features. PHP 7.x return types działają. Sprawdź oryginał — miał ": array", ": void", ": int", ": string" — zachowaj je. + + Utwórz autoload/Domain/Releases/UpdateRepository.php: + - namespace Domain\Releases; + - konstruktor: __construct($db, $settings) — settings potrzebne do update_key i wersji + - Przenieś logikę z class.Update.php: metoda update() + - Zastąp global $mdb → $this->db, global $settings → $this->settings + - Wywołania \S::* zachowaj (klasa S jest dostępna globalnie) + + Zaktualizuj autoload/admin/factory/class.Releases.php: + - Zastąp każdą metodę wrapperem: global $mdb; $repo = new \Domain\Releases\ReleasesRepository($mdb); return $repo->method(...) + - Zachowaj dokładnie te same sygnatury + + Zaktualizuj autoload/admin/factory/class.Update.php: + - Zastąp metodę update() wrapperem: + global $mdb, $settings; $repo = new \Domain\Releases\UpdateRepository($mdb, $settings); return $repo->update(); + + + Grep: Domain\Releases istnieje w obu nowych plikach repo + Grep: new \Domain\Releases\ReleasesRepository istnieje w class.Releases.php + Grep: new \Domain\Releases\UpdateRepository istnieje w class.Update.php + Brak bezpośrednich zapytań $mdb-> w factory wrapperach + + AC-3 i AC-4 satisfied: Releases + Update repos + wrapper delegation + + + + + + +## DO NOT CHANGE +- autoload/autoloader.php (PSR-4 mapowanie już obejmuje Domain\) +- autoload/Domain/Authors/, autoload/Domain/Newsletter/ (ukończone w Phase 4) +- autoload/Domain/Scontainers/, autoload/Domain/Banners/ (ukończone w Phase 3) +- Żadne inne pliki poza listą files_modified +- cron.php entry point — nie modyfikuj (klasa Cron nadal globalna) + +## SCOPE LIMITS +- Tylko Domain repositories i factory wrappers — bez Admin\ controllers +- Bez zmian w tabelach bazy danych ani SQL schema +- Bez refaktoryzacji metod — 1:1 przeniesienie logiki + + + + +Przed deklaracją ukończenia: +- [ ] Grep: autoload/Domain/SeoAdditional/SeoAdditionalRepository.php istnieje +- [ ] Grep: autoload/Domain/Cron/CronRepository.php istnieje +- [ ] Grep: autoload/Domain/Releases/ReleasesRepository.php istnieje +- [ ] Grep: autoload/Domain/Releases/UpdateRepository.php istnieje +- [ ] Grep: class.SeoAdditional.php zawiera "new \Domain\SeoAdditional" +- [ ] Grep: class.Cron.php zawiera "new \Domain\Cron" +- [ ] Grep: class.Releases.php zawiera "new \Domain\Releases" +- [ ] Grep: class.Update.php zawiera "new \Domain\Releases" +- [ ] Brak syntax errors (php -l na każdym nowym pliku) + + + +- 4 nowe Domain repository pliki utworzone +- 4 legacy klasy zaktualizowane do wrapper delegation +- Zero zmian w logice biznesowej (1:1 migracja) +- PHP < 8.0 kompatybilność zachowana +- Brak globals w repozytoriach + + + +Po ukończeniu utwórz: .paul/phases/05-domain-seoadditional-cron-releases/05-01-SUMMARY.md + diff --git a/.paul/phases/05-domain-seoadditional-cron-releases/05-01-SUMMARY.md b/.paul/phases/05-domain-seoadditional-cron-releases/05-01-SUMMARY.md new file mode 100644 index 0000000..36fc5f1 --- /dev/null +++ b/.paul/phases/05-domain-seoadditional-cron-releases/05-01-SUMMARY.md @@ -0,0 +1,125 @@ +--- +phase: 05-domain-seoadditional-cron-releases +plan: 01 +subsystem: domain +tags: [php, domain, repository, wrapper-delegation, medoo] + +requires: + - phase: 01-infrastructure + provides: PSR-4 autoloader mapujący Domain\ + +provides: + - Domain\SeoAdditional\SeoAdditionalRepository + - Domain\Cron\CronRepository + - Domain\Releases\ReleasesRepository + - Domain\Releases\UpdateRepository + - Wrapper delegation dla 4 legacy klas + +affects: + - 11-admin-newsletter-emails-seoadditional + - 13-admin-releases-update + +tech-stack: + added: [] + patterns: + - "Wrapper delegation: admin\\factory i global class.Cron delegują do Domain\\ repos" + - "UpdateRepository przyjmuje ($db, $settings) — dwa globals jako explicit params" + +key-files: + created: + - autoload/Domain/SeoAdditional/SeoAdditionalRepository.php + - autoload/Domain/Cron/CronRepository.php + - autoload/Domain/Releases/ReleasesRepository.php + - autoload/Domain/Releases/UpdateRepository.php + modified: + - autoload/admin/factory/class.SeoAdditional.php + - autoload/class.Cron.php + - autoload/admin/factory/class.Releases.php + - autoload/admin/factory/class.Update.php + +key-decisions: + - "UpdateRepository przyjmuje ($db, $settings) w konstruktorze — settings potrzebny do update_key" + - "Cron helper methods (get_site_meta_*) zostały private w CronRepository — były wywoływane tylko wewnętrznie" + - "class.Cron.php zachowuje brak namespace (klasa globalna) — entry point cron.php używa bezpośrednio" + +patterns-established: + - "Wszystkie Domain repos: konstruktor($db), brak globals, metody camelCase" + - "Factory wrappers: new repo per call, global $mdb w każdej metodzie" + +duration: ~5min +started: 2026-04-26T00:00:00Z +completed: 2026-04-26T00:05:00Z +--- + +# Phase 5 Plan 01: SeoAdditional + Cron + Releases Summary + +**4 Domain repositories ukończone — Domain layer kompletny (13/13 repos), wrapper delegation dla SeoAdditional, Cron, Releases i Update.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~5min | +| Started | 2026-04-26 | +| Completed | 2026-04-26 | +| Tasks | 3 completed | +| Files modified | 8 (4 created, 4 updated) | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: SeoAdditional Repository | Pass | 3 metody: elementDelete, elementSave, elementDetails | +| AC-2: Cron Repository | Pass | 3 public + 12 private helper methods, brak namespace w wrapperze | +| AC-3: Releases Repository | Pass | 9 metod + private zipDir helper | +| AC-4: Update Repository | Pass | Pełna logika update(), ($db, $settings) w konstruktorze | + +## Accomplishments + +- Ukończono Domain layer: wszystkie 13 repozytoriów w `Domain\` namespace +- SeoAdditional: prosta migracja 3 CRUD metod z factory do repo +- Cron: migracja dużej klasy (15 metod) — helper methods stały się private w repo +- Releases: 9 metod + prywatny helper zipDir, zachowane PHP 7.x return type hints +- UpdateRepository: jako jedyny repo przyjmuje 2 parametry ($db, $settings) — settings wymagane dla update_key + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `autoload/Domain/SeoAdditional/SeoAdditionalRepository.php` | Created | SEO dodatkowe wpisy — CRUD | +| `autoload/Domain/Cron/CronRepository.php` | Created | Cron jobs — crawling i analiza stron | +| `autoload/Domain/Releases/ReleasesRepository.php` | Created | Zarządzanie wersjami i licencjami | +| `autoload/Domain/Releases/UpdateRepository.php` | Created | Mechanizm auto-update (pobieranie paczek ZIP) | +| `autoload/admin/factory/class.SeoAdditional.php` | Modified | Wrapper → deleguje do Domain\SeoAdditional | +| `autoload/class.Cron.php` | Modified | Wrapper → deleguje do Domain\Cron (brak namespace) | +| `autoload/admin/factory/class.Releases.php` | Modified | Wrapper → deleguje do Domain\Releases\ReleasesRepository | +| `autoload/admin/factory/class.Update.php` | Modified | Wrapper → deleguje do Domain\Releases\UpdateRepository | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| UpdateRepository($db, $settings) | Metoda update() używa $settings['update_key'] — musi być w konstruktorze | Admin\Update\UpdateController też przekaże oba parametry | +| Cron helpers → private | Metody get_site_meta_* były wywoływane tylko przez getSiteOtherLinks() | Czystsza enkapsulacja, brak public API dla wewnętrznych helperów | +| class.Cron.php bez namespace | Zachowanie 100% compat — cron.php używa `Cron::` bez backslasha | Klasa globalna pozostaje globalna do Phase 19 cleanup | + +## Deviations from Plan + +None — plan wykonany dokładnie jak zaplanowano. + +## Next Phase Readiness + +**Ready:** +- Domain layer kompletny (13 repozytoriów) — gotowy pod Admin\ controllers +- Phase 6: Admin Base Infrastructure może startować (nie zależy od Domain\Cron/Releases/SeoAdditional bezpośrednio) +- Phase 11 (Admin: Newsletter + Emails + SeoAdditional) i Phase 13 (Admin: Releases + Update) mają gotowe Domain repos + +**Concerns:** +- Brak — wszystkie dependency spełnione + +**Blockers:** +- None + +--- +*Phase: 05-domain-seoadditional-cron-releases, Plan: 01* +*Completed: 2026-04-26* diff --git a/autoload/Domain/Cron/CronRepository.php b/autoload/Domain/Cron/CronRepository.php new file mode 100644 index 0000000..1f4149a --- /dev/null +++ b/autoload/Domain/Cron/CronRepository.php @@ -0,0 +1,495 @@ +db = $db; + } + + public function automaticUpdateSites() + { + $results = $this->db->query( "SELECT id, url FROM projects WHERE automatic_update = 1 AND DATE_ADD( last_update, INTERVAL 1 WEEK ) <= '" . date( 'Y-m-d H:i:s' ) . "'" )->fetchAll(); + if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row ) + { + $this->db->delete( 'project_links_internal', [ 'AND' => [ 'project_id' => $row['id'], 'parent_id[!]' => null ] ] ); + $this->db->delete( 'project_links_external', [ 'project_id' => $row['id'] ] ); + $this->db->update( 'project_links_internal', [ 'visited' => 0 ], [ 'project_id' => $row['id'] ] ); + + $this->db->update( 'projects', [ 'last_update' => date( 'Y-m-d H:i:s' ) ], [ 'id' => $row['id'] ] ); + + return [ 'status' => 'ok', 'msg' => 'Ponawiam sprawdzanie strony ' . $row['url'] . '' ]; + } + return [ 'status' => 'empty' ]; + } + + public function getSiteMainLinks() + { + $results = $this->db->query( 'SELECT id, url FROM projects WHERE id NOT IN ( SELECT project_id FROM project_links_internal GROUP BY project_id ) AND enabled = 1 LIMIT 1' )->fetchAll(); + if ( is_array( $results ) and !empty ( $results ) ) foreach ( $results as $row ) + { + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_URL, $row['url'] ); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); + curl_setopt( $ch, CURLOPT_VERBOSE, 1 ); + curl_setopt( $ch, CURLOPT_TIMEOUT, 60 ); + curl_setopt( $ch, CURLOPT_HEADER, true ); + curl_setopt( $ch, CURLOPT_CAINFO, 'cacert.pem' ); + curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); + curl_setopt( $ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.103 Safari/537.36' ); + $response = curl_exec( $ch ); + curl_close ( $ch ); + + if ( !curl_errno( $ch ) ) + { + $this->db->insert( 'project_links_internal', [ + 'project_id' => $row['id'], + 'url' => $row['url'], + 'parent_id' => null + ] ); + + $doc = new \DOMDocument; + $doc->loadHTML( $response ); + foreach ( $doc->getElementsByTagName( 'a' ) as $link ) + { + $url = $link->getAttribute( 'href' ); + + if ( \S::is_url_internal( $row['url'], $url ) ) + { + if ( strpos( $url, '#' ) !== false ) + $url = rtrim( substr( $url, 0, strpos( $url, '#' ) ), '?,#' ); + + $url = \S::modify_internal_link( $row['url'], $url ); + + if ( !filter_var( $url, FILTER_VALIDATE_URL ) === false and !$this->db->count( 'project_links_internal', [ 'AND' => [ 'project_id' => $row['id'], 'url' => $url ] ] ) ) + { + $this->db->insert( 'project_links_internal', [ + 'project_id' => $row['id'], + 'url' => $url + ] ); + } + } + } + return [ 'status' => 'ok', 'msg' => 'Pobieram linki dla strony ' . $row['url'] . '' ]; + } + else + return [ 'status' => 'ok', 'msg' => 'Błąd podczas pobierania strony ' . $row['url'] . '' ]; + } + return [ 'status' => 'empty' ]; + } + + public function getSiteOtherLinks() + { + $results = $this->db->query( 'SELECT ' + . 'pli.id, project_id, pli.url, p.url AS project_url ' + . 'FROM ' + . 'project_links_internal AS pli ' + . 'INNER JOIN projects AS p ON p.id = pli.project_id ' + . 'WHERE ' + . 'visited = 0 AND enabled = 1 ' + . 'LIMIT 1' )->fetchAll(); + if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row ) + { + $url = parse_url( $row['url'] ); + + $ch = curl_init(); + curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); + curl_setopt( $ch, CURLOPT_VERBOSE, 1 ); + curl_setopt( $ch, CURLOPT_TIMEOUT, 60 ); + curl_setopt( $ch, CURLOPT_COOKIEFILE, 'temp/cookie.txt' ); + curl_setopt( $ch, CURLOPT_COOKIEJAR, 'temp/cookie.txt' ); + curl_setopt( $ch, CURLOPT_CAINFO, 'cacert.pem' ); + curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); + curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true ); + curl_setopt( $ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.103 Safari/537.36' ); + + curl_setopt( $ch, CURLOPT_URL, 'http://' . $url['host'] ); + $response = curl_exec( $ch ); + + curl_setopt( $ch, CURLOPT_URL, $row['url'] ); + $response = curl_exec( $ch ); + $content_type = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE ); + $code = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); + curl_close ( $ch ); + + if ( !curl_errno( $ch ) and ( $code == 200 or $code == 301 ) and strpos( $content_type, 'text/html' ) !== false ) + { + $this->getSiteMetaTitle( $row['id'], $response ); + $this->getSiteMetaKeywords( $row['id'], $response ); + $this->getSiteMetaDescription( $row['id'], $response ); + $this->getSiteMetaRobots( $row['id'], $response ); + $this->getSiteMetaGooglebot( $row['id'], $response ); + $this->getSiteCodeLenght( $row['id'], $response ); + $this->getSiteTextLenght( $row['id'], $response ); + $this->getSiteCanonical( $row['id'], $response ); + $this->getTableExists( $row['id'], $response ); + $this->getIframeExists( $row['id'], $response ); + $this->getH1Exists( $row['id'], $response ); + $this->getImagesWithoutAlt( $row['id'], $response ); + + /* pobranie linków ze strony */ + $doc = new \DOMDocument; + $doc->loadHTML( $response ); + + foreach ( $doc->getElementsByTagName( 'a' ) as $link ) + { + $url = $link->getAttribute( 'href' ); + + /* linki wewnętrzne na danej postronie */ + if ( \S::is_url_internal( $row['project_url'], $url ) ) + { + if ( strpos( $url, '#' ) !== false ) + $url = rtrim( substr( $url, 0, strpos( $url, '#' ) ), '?,#' ); + + $url = \S::modify_internal_link( $row['project_url'], $url, $row['url'] ); + $info = pathinfo( $url ); + + if ( !filter_var( $url, FILTER_VALIDATE_URL ) === false and !in_array( strtolower( $info['extension'] ), \S::not_html_format() ) and !$this->db->count( 'project_links_internal', [ + 'AND' => [ + 'project_id' => $row['project_id'], + 'url' => $url + ] + ] ) ) + { + $this->db->insert( 'project_links_internal', [ + 'project_id' => $row['project_id'], + 'url' => $url, + 'visited' => 0, + 'parent_id' => $row['id'], + 'response' => $response + ] ); + } + } + /* linki zewnętrzne na danej podstronie */ + else + { + $link->getAttribute( 'rel' ) == 'nofollow' ? $nofollow = 1 : $nofollow = 0; + + $this->db->insert( 'project_links_external', [ + 'project_id' => $row['project_id'], + 'link_id' => $row['id'], + 'url' => $link->getAttribute( 'href' ), + 'nofollow' => $nofollow, + 'title' => $link->getAttribute( 'title' ) + ] ); + } + } + + $this->db->update( 'project_links_internal', [ + 'visited' => 1, + 'content_type' => $content_type, + 'response_code' => $code, + 'response' => $response + ], [ + 'id' => $row['id'] + ] ); + + return [ 'status' => 'ok', 'msg' => 'Pobieram informacje dla strony ' . $row['url'] . '' ]; + } + else if ( $code == 404 or strpos( $content_type, 'text/html' ) === false ) + { + $this->db->update( 'project_links_internal', [ + 'visited' => 1, + 'deleted' => 1, + 'content_type' => $content_type, + 'response_code' => $code + ], [ + 'id' => $row['id'] + ] ); + + return [ 'status' => 'ok', 'msg' => 'Pobieram informacje dla strony ' . $row['url'] . '' ]; + } + else if ( $code !== 200 and strpos( $content_type, 'text/html' ) !== false ) + { + $this->db->update( 'project_links_internal', [ + 'visited' => 1, + 'content_type' => $content_type, + 'response_code' => $code, + 'response' => $response + ], [ + 'id' => $row['id'] + ] ); + + return [ 'status' => 'ok', 'msg' => 'Pobieram informacje dla strony ' . $row['url'] . '' ]; + } + else + return [ 'status' => 'ok', 'msg' => 'Błąd podczas pobierania strony ' . $row['url'] . '' ]; + } + return [ 'status' => 'empty' ]; + } + + private function getImagesWithoutAlt( $urlId, $response ) + { + $doc = new \DOMDocument; + $doc->loadHTML( $response ); + $images = $doc->getElementsByTagName("img"); + + $have_images_without_alt = 0; + foreach ( $images as $img ) + { + if ( !$img->getAttribute( 'alt' ) ) + $have_images_without_alt = 1; + } + + $this->db->update( 'project_links_internal', [ 'have_images_without_alt' => $have_images_without_alt ], [ 'id' => $urlId ] ); + } + + private function getTableExists( $urlId, $response ) + { + $doc = new \DOMDocument; + $doc->loadHTML( $response ); + $count = $doc->getElementsByTagName("table"); + + $this->db->update( 'project_links_internal', [ 'have_table' => $count->length ? 1 : 0 ], [ 'id' => $urlId ] ); + } + + private function getIframeExists( $urlId, $response ) + { + $doc = new \DOMDocument; + $doc->loadHTML( $response ); + $count = $doc->getElementsByTagName("iframe"); + + $this->db->update( 'project_links_internal', [ 'have_iframe' => $count->length ? 1 : 0 ], [ 'id' => $urlId ] ); + } + + private function getH1Exists( $urlId, $response ) + { + $doc = new \DOMDocument; + $doc->loadHTML( $response ); + $count = $doc->getElementsByTagName("h1"); + $this->db->update( 'project_links_internal', [ 'have_h1' => $count->length ? 1 : 0 ], [ 'id' => $urlId ] ); + } + + private function getSiteMetaTitle( $urlId, $response ) + { + $title = ''; + + preg_match('/([^>]*)<\/title>/si', $response, $match ); + + if ( isset( $match ) && is_array( $match ) && count( $match ) > 0 ) + $title = (string)strip_tags( $match[1] ); + + if ( !$title ) + { + preg_match_all('/<[\s]*meta[\s]*name="og:?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); + + if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) + { + $originals = $match[0]; + $names = $match[1]; + $values = $match[2]; + + if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) + { + $metaTags = array(); + for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) + { + $metaTags[ $names[$i] ] = array( + 'html' => htmlentities( $originals[$i] ), + 'value' => $values[$i] + ); + } + } + $title = (string)$metaTags['title']['value']; + } + } + + $this->db->update( 'project_links_internal', [ 'title' => $title ], [ 'id' => $urlId ] ); + } + + private function getSiteCanonical( $urlId, $response ) + { + $doc = new \DOMDocument; + $doc->loadHTML( $response ); + foreach ( $doc->getElementsByTagName( 'link' ) as $link ) + { + $rel = $link->getAttribute( 'rel' ); + + if ( $rel == 'canonical' ) + { + $canonical = $link->getAttribute( 'href' ); + } + } + + $this->db->update( 'project_links_internal', [ 'canonical' => $canonical ], [ 'id' => $urlId ] ); + } + + private function getSiteMetaKeywords( $urlId, $response ) + { + $meta_keywords = ''; + + preg_match_all( '/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match ); + + if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) + { + $originals = $match[0]; + $names = $match[1]; + $values = $match[2]; + + if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) + { + $metaTags = array(); + for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) + { + $metaTags[ $names[$i] ] = array( + 'html' => htmlentities( $originals[$i] ), + 'value' => $values[$i] + ); + } + } + $meta_keywords = (string)$metaTags['keywords']['value']; + } + + if ( !$meta_keywords ) + { + preg_match_all( '/<[\s]*meta[\s]*property="og:?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match ); + + if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) + { + $originals = $match[0]; + $names = $match[1]; + $values = $match[2]; + + if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) + { + $metaTags = array(); + for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) + { + $metaTags[ $names[$i] ] = array( + 'html' => htmlentities( $originals[$i] ), + 'value' => $values[$i] + ); + } + } + $meta_keywords = (string)$metaTags['keywords']['value']; + } + } + + $this->db->update( 'project_links_internal', [ 'meta_keywords' => $meta_keywords ], [ 'id' => $urlId ] ); + } + + private function getSiteMetaDescription( $urlId, $response ) + { + $meta_description = ''; + + preg_match_all('/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); + + if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) + { + $originals = $match[0]; + $names = $match[1]; + $values = $match[2]; + + if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) + { + $metaTags = array(); + for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) + { + $metaTags[ $names[$i] ] = array( + 'html' => htmlentities( $originals[$i] ), + 'value' => $values[$i] + ); + } + } + $meta_description = (string)$metaTags['description']['value']; + } + + if ( !$meta_description ) + { + preg_match_all( '/<[\s]*meta[\s]*property="og:?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match ); + + if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) + { + $originals = $match[0]; + $names = $match[1]; + $values = $match[2]; + + if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) + { + $metaTags = array(); + for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) + { + $metaTags[ $names[$i] ] = array( + 'html' => htmlentities( $originals[$i] ), + 'value' => $values[$i] + ); + } + } + $meta_description = (string)$metaTags['description']['value']; + } + } + + $this->db->update( 'project_links_internal', [ 'meta_description' => $meta_description ], [ 'id' => $urlId ] ); + } + + private function getSiteMetaRobots( $urlId, $response ) + { + $meta_robots = ''; + + preg_match_all('/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); + + if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) + { + $originals = $match[0]; + $names = $match[1]; + $values = $match[2]; + + if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) + { + $metaTags = array(); + for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) + { + $metaTags[ $names[$i] ] = array( + 'html' => htmlentities( $originals[$i] ), + 'value' => $values[$i] + ); + } + } + $meta_robots = (string)$metaTags['robots']['value']; + } + + $this->db->update( 'project_links_internal', [ 'meta_robots' => $meta_robots ], [ 'id' => $urlId ] ); + } + + private function getSiteMetaGooglebot( $urlId, $response ) + { + $meta_googlebot = ''; + + preg_match_all('/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); + + if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) + { + $originals = $match[0]; + $names = $match[1]; + $values = $match[2]; + + if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) + { + $metaTags = array(); + for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) + { + $metaTags[ $names[$i] ] = array( + 'html' => htmlentities( $originals[$i] ), + 'value' => $values[$i] + ); + } + } + $meta_googlebot = (string)$metaTags['googlebot']['value']; + } + + $this->db->update( 'project_links_internal', [ 'meta_googlebot' => $meta_googlebot ], [ 'id' => $urlId ] ); + } + + private function getSiteCodeLenght( $urlId, $response ) + { + $this->db->update( 'project_links_internal', [ 'code_lenght' => strlen( $response ) ], [ 'id' => $urlId ] ); + } + + private function getSiteTextLenght( $urlId, $response ) + { + $this->db->update( 'project_links_internal', [ 'text_lenght' => strlen( \S::strip_html_tags( $response ) ) ], [ 'id' => $urlId ] ); + } +} diff --git a/autoload/Domain/Releases/ReleasesRepository.php b/autoload/Domain/Releases/ReleasesRepository.php new file mode 100644 index 0000000..741bf8d --- /dev/null +++ b/autoload/Domain/Releases/ReleasesRepository.php @@ -0,0 +1,101 @@ +<?php +namespace Domain\Releases; + +class ReleasesRepository +{ + private $db; + + public function __construct($db) + { + $this->db = $db; + } + + public function getVersions(): array + { + $rows = $this->db->select('pp_update_versions', '*', ['ORDER' => ['version' => 'DESC']]); + if (!$rows) return []; + foreach ($rows as &$row) + $row['zip_exists'] = file_exists('../updates/' . $this->zipDir($row['version']) . '/ver_' . $row['version'] . '.zip'); + return $rows; + } + + public function promote(string $version): void + { + $this->db->update('pp_update_versions', + ['channel' => 'stable', 'promoted_at' => date('Y-m-d H:i:s')], + ['version' => $version] + ); + } + + public function demote(string $version): void + { + $this->db->update('pp_update_versions', + ['channel' => 'beta', 'promoted_at' => null], + ['version' => $version] + ); + } + + public function discoverVersions(): int + { + $known = array_flip($this->db->select('pp_update_versions', 'version', []) ?: []); + $zips = glob('../updates/*/ver_*.zip') ?: []; + $added = 0; + foreach ($zips as $path) { + preg_match('/ver_([0-9.]+)\.zip$/', $path, $m); + if (!$m) continue; + $ver = $m[1]; + if (isset($known[$ver])) continue; + $this->db->insert('pp_update_versions', [ + 'version' => $ver, + 'channel' => 'beta', + 'created_at' => date('Y-m-d H:i:s'), + ]); + $known[$ver] = true; + $added++; + } + return $added; + } + + public function getLicenses(): array + { + return $this->db->select('pp_update_licenses', '*', ['ORDER' => ['domain' => 'ASC']]) ?: []; + } + + public function getLicense(int $id): array + { + return $this->db->get('pp_update_licenses', '*', ['id' => $id]) ?: []; + } + + public function saveLicense(array $data): void + { + $row = [ + 'key' => trim($data['key'] ?? ''), + 'domain' => trim($data['domain'] ?? ''), + 'valid_to_date' => $data['valid_to_date'] ?: null, + 'valid_to_version' => $data['valid_to_version'] ?: null, + 'beta' => (int)(bool)($data['beta'] ?? 0), + 'note' => trim($data['note'] ?? ''), + ]; + if (!empty($data['id'])) + $this->db->update('pp_update_licenses', $row, ['id' => (int)$data['id']]); + else + $this->db->insert('pp_update_licenses', $row); + } + + public function deleteLicense(int $id): void + { + $this->db->delete('pp_update_licenses', ['id' => $id]); + } + + public function toggleBeta(int $id): void + { + $license = $this->db->get('pp_update_licenses', ['id', 'beta'], ['id' => $id]); + if ($license) + $this->db->update('pp_update_licenses', ['beta' => $license['beta'] ? 0 : 1], ['id' => $id]); + } + + private function zipDir(string $version): string + { + return substr($version, 0, strlen($version) - (strlen($version) == 5 ? 2 : 1)) . '0'; + } +} diff --git a/autoload/Domain/Releases/UpdateRepository.php b/autoload/Domain/Releases/UpdateRepository.php new file mode 100644 index 0000000..beb386e --- /dev/null +++ b/autoload/Domain/Releases/UpdateRepository.php @@ -0,0 +1,163 @@ +<?php +namespace Domain\Releases; + +class UpdateRepository +{ + private $db; + private $settings; + + public function __construct($db, $settings) + { + $this->db = $db; + $this->settings = $settings; + } + + public function update() + { + \S::delete_session( 'new-version' ); + + $versions = file_get_contents( 'https://www.cmspro.project-dc.pl/updates/versions.php?key=' . urlencode( $this->settings['update_key'] ) ); + $versions = explode( PHP_EOL, $versions ); + + foreach ( $versions as $ver ) + { + $ver = trim( $ver ); + if ( (float)$ver > (float)\S::get_version() ) + { + if ( strlen( $ver ) == 5 ) + $dir = substr( $ver, 0, strlen( $ver ) - 2 ) . 0; + else + $dir = substr( $ver, 0, strlen( $ver ) - 1 ) . 0; + + $baseUrl = 'https://www.cmspro.project-dc.pl/updates/' . $dir; + + /* pobranie paczki ZIP */ + $file = file_get_contents( $baseUrl . '/ver_' . $ver . '.zip' ); + + $dlHandler = fopen( 'update.zip' , 'w' ); + if ( !fwrite( $dlHandler, $file ) ) + return false; + fclose( $dlHandler ); + + if ( !file_exists( 'update.zip' ) ) + return false; + + /* pobranie manifestu JSON (nowy system) lub fallback na legacy _sql.txt / _files.txt */ + $manifest = null; + $manifestJson = @file_get_contents( $baseUrl . '/ver_' . $ver . '_manifest.json' ); + if ( $manifestJson ) + { + if ( substr( $manifestJson, 0, 3 ) === "\xEF\xBB\xBF" ) + $manifestJson = substr( $manifestJson, 3 ); + $manifest = @json_decode( $manifestJson, true ); + } + + if ( is_array( $manifest ) ) + { + /* weryfikacja checksum SHA256 */ + if ( !empty( $manifest['checksum_zip'] ) ) + { + $expectedHash = str_replace( 'sha256:', '', $manifest['checksum_zip'] ); + $actualHash = hash_file( 'sha256', 'update.zip' ); + if ( $expectedHash !== $actualHash ) + { + unlink( 'update.zip' ); + return false; + } + } + + /* aktualizacja bazy danych z manifestu */ + if ( !empty( $manifest['sql'] ) && is_array( $manifest['sql'] ) ) + { + foreach ( $manifest['sql'] as $query ) + { + $query = trim( $query ); + if ( $query ) + $this->db -> query( $query ); + } + } + + /* usuwanie plikow z manifestu */ + if ( !empty( $manifest['files']['deleted'] ) && is_array( $manifest['files']['deleted'] ) ) + { + foreach ( $manifest['files']['deleted'] as $filePath ) + { + $fullPath = '../' . $filePath; + if ( file_exists( $fullPath ) ) + unlink( $fullPath ); + } + } + + /* usuwanie katalogow z manifestu */ + if ( !empty( $manifest['directories_deleted'] ) && is_array( $manifest['directories_deleted'] ) ) + { + foreach ( $manifest['directories_deleted'] as $dirPath ) + { + $fullPath = '../' . $dirPath; + if ( is_dir( $fullPath ) ) + \S::delete_dir( $fullPath ); + } + } + } + else + { + /* legacy: aktualizacja bazy danych z _sql.txt */ + $sql = @file_get_contents( $baseUrl . '/ver_' . $ver . '_sql.txt' ); + if ( $sql ) + { + $sql = explode( PHP_EOL, $sql ); + if ( is_array( $sql ) ) foreach ( $sql as $query ) + { + $query = trim( $query ); + if ( $query ) + $this->db -> query( $query ); + } + } + + /* legacy: usuwanie zbednych plikow z _files.txt */ + $lines = @file_get_contents( $baseUrl . '/ver_' . $ver . '_files.txt' ); + if ( $lines ) + { + $lines = explode( PHP_EOL, $lines ); + if ( is_array( $lines ) ) foreach ( $lines as $line ) + { + if ( strpos( $line, 'F: ' ) !== false ) + { + $delFile = substr( $line, 3, strlen( $line ) ); + if ( file_exists( $delFile ) ) + unlink( $delFile ); + } + + if ( strpos( $line, 'D: ' ) !== false ) + { + $delDir = substr( $line, 3, strlen( $line ) ); + if ( is_dir( $delDir ) ) + \S::delete_dir( $delDir ); + } + } + } + } + + /* wgrywanie nowych plikow */ + $file_name = 'update.zip'; + + $path = pathinfo( realpath( $file_name ), PATHINFO_DIRNAME ); + $path = substr( $path, 0, strlen( $path ) - 5 ); + $zip = new \ZipArchive; + $res = $zip -> open( $file_name ); + if ( $res === TRUE ) + { + $zip -> extractTo( $path ); + $zip -> close(); + unlink( $file_name ); + } + + $updateThis = fopen( '../libraries/version.ini', 'w' ); + fwrite( $updateThis, $ver ); + fclose( $updateThis ); + + return true; + } + } + } +} diff --git a/autoload/Domain/SeoAdditional/SeoAdditionalRepository.php b/autoload/Domain/SeoAdditional/SeoAdditionalRepository.php new file mode 100644 index 0000000..0e04038 --- /dev/null +++ b/autoload/Domain/SeoAdditional/SeoAdditionalRepository.php @@ -0,0 +1,57 @@ +<?php +namespace Domain\SeoAdditional; + +class SeoAdditionalRepository +{ + private $db; + + public function __construct($db) + { + $this->db = $db; + } + + public function elementDelete($elementId) + { + return $this->db->delete('pp_seo_additional', ['id' => (int)$elementId]); + } + + public function elementSave($id, $url, $status, $title, $keywords, $description, $text) + { + if (!$id) + { + if ($this->db->insert('pp_seo_additional', [ + 'url' => $url, + 'status' => $status == 'on' ? 1 : 0, + 'title' => $title, + 'keywords' => $keywords, + 'description' => $description, + 'text' => $text + ])) + { + \S::delete_cache(); + return $this->db->id(); + } + } + else + { + $this->db->update('pp_seo_additional', [ + 'url' => $url, + 'status' => $status == 'on' ? 1 : 0, + 'title' => $title, + 'keywords' => $keywords, + 'description' => $description, + 'text' => $text + ], [ + 'id' => (int)$id + ]); + + \S::delete_cache(); + return $id; + } + } + + public function elementDetails($elementId) + { + return $this->db->get('pp_seo_additional', '*', ['id' => (int)$elementId]); + } +} diff --git a/autoload/admin/factory/class.Releases.php b/autoload/admin/factory/class.Releases.php index 950c473..fd89944 100644 --- a/autoload/admin/factory/class.Releases.php +++ b/autoload/admin/factory/class.Releases.php @@ -6,98 +6,63 @@ class Releases public static function get_versions(): array { global $mdb; - $rows = $mdb->select('pp_update_versions', '*', ['ORDER' => ['version' => 'DESC']]); - if (!$rows) return []; - foreach ($rows as &$row) - $row['zip_exists'] = file_exists('../updates/' . self::zip_dir($row['version']) . '/ver_' . $row['version'] . '.zip'); - return $rows; + $repo = new \Domain\Releases\ReleasesRepository($mdb); + return $repo->getVersions(); } public static function promote(string $version): void { global $mdb; - $mdb->update('pp_update_versions', - ['channel' => 'stable', 'promoted_at' => date('Y-m-d H:i:s')], - ['version' => $version] - ); + $repo = new \Domain\Releases\ReleasesRepository($mdb); + $repo->promote($version); } public static function demote(string $version): void { global $mdb; - $mdb->update('pp_update_versions', - ['channel' => 'beta', 'promoted_at' => null], - ['version' => $version] - ); + $repo = new \Domain\Releases\ReleasesRepository($mdb); + $repo->demote($version); } public static function discover_versions(): int { global $mdb; - $known = array_flip($mdb->select('pp_update_versions', 'version', []) ?: []); - $zips = glob('../updates/*/ver_*.zip') ?: []; - $added = 0; - foreach ($zips as $path) { - preg_match('/ver_([0-9.]+)\.zip$/', $path, $m); - if (!$m) continue; - $ver = $m[1]; - if (isset($known[$ver])) continue; - $mdb->insert('pp_update_versions', [ - 'version' => $ver, - 'channel' => 'beta', - 'created_at' => date('Y-m-d H:i:s'), - ]); - $known[$ver] = true; - $added++; - } - return $added; + $repo = new \Domain\Releases\ReleasesRepository($mdb); + return $repo->discoverVersions(); } public static function get_licenses(): array { global $mdb; - return $mdb->select('pp_update_licenses', '*', ['ORDER' => ['domain' => 'ASC']]) ?: []; + $repo = new \Domain\Releases\ReleasesRepository($mdb); + return $repo->getLicenses(); } public static function get_license(int $id): array { global $mdb; - return $mdb->get('pp_update_licenses', '*', ['id' => $id]) ?: []; + $repo = new \Domain\Releases\ReleasesRepository($mdb); + return $repo->getLicense($id); } public static function save_license(array $data): void { global $mdb; - $row = [ - 'key' => trim($data['key'] ?? ''), - 'domain' => trim($data['domain'] ?? ''), - 'valid_to_date' => $data['valid_to_date'] ?: null, - 'valid_to_version' => $data['valid_to_version'] ?: null, - 'beta' => (int)(bool)($data['beta'] ?? 0), - 'note' => trim($data['note'] ?? ''), - ]; - if (!empty($data['id'])) - $mdb->update('pp_update_licenses', $row, ['id' => (int)$data['id']]); - else - $mdb->insert('pp_update_licenses', $row); + $repo = new \Domain\Releases\ReleasesRepository($mdb); + $repo->saveLicense($data); } public static function delete_license(int $id): void { global $mdb; - $mdb->delete('pp_update_licenses', ['id' => $id]); + $repo = new \Domain\Releases\ReleasesRepository($mdb); + $repo->deleteLicense($id); } public static function toggle_beta(int $id): void { global $mdb; - $license = $mdb->get('pp_update_licenses', ['id', 'beta'], ['id' => $id]); - if ($license) - $mdb->update('pp_update_licenses', ['beta' => $license['beta'] ? 0 : 1], ['id' => $id]); - } - - private static function zip_dir(string $version): string - { - return substr($version, 0, strlen($version) - (strlen($version) == 5 ? 2 : 1)) . '0'; + $repo = new \Domain\Releases\ReleasesRepository($mdb); + $repo->toggleBeta($id); } } diff --git a/autoload/admin/factory/class.SeoAdditional.php b/autoload/admin/factory/class.SeoAdditional.php index db3ef58..c4414c1 100644 --- a/autoload/admin/factory/class.SeoAdditional.php +++ b/autoload/admin/factory/class.SeoAdditional.php @@ -5,51 +5,21 @@ class SeoAdditional public static function element_delete( $element_id ) { global $mdb; - return $mdb -> delete( 'pp_seo_additional', [ 'id' => (int)$element_id ] ); + $repo = new \Domain\SeoAdditional\SeoAdditionalRepository($mdb); + return $repo->elementDelete($element_id); } - + public static function element_save( $id, $url, $status, $title, $keywords, $description, $text ) { global $mdb; - - if ( !$id ) - { - if ( $mdb -> insert( 'pp_seo_additional', [ - 'url' => $url, - 'status' => $status == 'on' ? 1 : 0, - 'title' => $title, - 'keywords' => $keywords, - 'description' => $description, - 'text' => $text - ] ) ) - { - \S::delete_cache(); - return $mdb -> id(); - } - } - else - { - $mdb -> update( 'pp_seo_additional', [ - 'url' => $url, - 'status' => $status == 'on' ? 1 : 0, - 'title' => $title, - 'keywords' => $keywords, - 'description' => $description, - 'text' => $text + $repo = new \Domain\SeoAdditional\SeoAdditionalRepository($mdb); + return $repo->elementSave($id, $url, $status, $title, $keywords, $description, $text); + } - ], [ - 'id' => (int)$id - ] ); - - \S::delete_cache(); - return $id; - } - } - public static function element_details( $element_id ) { - global $mdb; - $result = $mdb -> get ( 'pp_seo_additional', '*', [ 'id' => (int)$element_id ] ); - return $result; + global $mdb; + $repo = new \Domain\SeoAdditional\SeoAdditionalRepository($mdb); + return $repo->elementDetails($element_id); } } diff --git a/autoload/admin/factory/class.Update.php b/autoload/admin/factory/class.Update.php index 6ecd966..6e29fe1 100644 --- a/autoload/admin/factory/class.Update.php +++ b/autoload/admin/factory/class.Update.php @@ -6,151 +6,7 @@ class Update public static function update() { global $mdb, $settings; - - \S::delete_session( 'new-version' ); - - $versions = file_get_contents( 'http://www.cmspro.project-dc.pl/updates/versions.php?key=' . $settings['update_key'] ); - $versions = explode( PHP_EOL, $versions ); - - foreach ( $versions as $ver ) - { - $ver = trim( $ver ); - if ( (float)$ver > (float)\S::get_version() ) - { - if ( strlen( $ver ) == 5 ) - $dir = substr( $ver, 0, strlen( $ver ) - 2 ) . 0; - else - $dir = substr( $ver, 0, strlen( $ver ) - 1 ) . 0; - - $baseUrl = 'http://www.cmspro.project-dc.pl/updates/' . $dir; - - /* pobranie paczki ZIP */ - $file = file_get_contents( $baseUrl . '/ver_' . $ver . '.zip' ); - - $dlHandler = fopen( 'update.zip' , 'w' ); - if ( !fwrite( $dlHandler, $file ) ) - return false; - fclose( $dlHandler ); - - if ( !file_exists( 'update.zip' ) ) - return false; - - /* pobranie manifestu JSON (nowy system) lub fallback na legacy _sql.txt / _files.txt */ - $manifest = null; - $manifestJson = @file_get_contents( $baseUrl . '/ver_' . $ver . '_manifest.json' ); - if ( $manifestJson ) - { - if ( substr( $manifestJson, 0, 3 ) === "\xEF\xBB\xBF" ) - $manifestJson = substr( $manifestJson, 3 ); - $manifest = @json_decode( $manifestJson, true ); - } - - if ( is_array( $manifest ) ) - { - /* weryfikacja checksum SHA256 */ - if ( !empty( $manifest['checksum_zip'] ) ) - { - $expectedHash = str_replace( 'sha256:', '', $manifest['checksum_zip'] ); - $actualHash = hash_file( 'sha256', 'update.zip' ); - if ( $expectedHash !== $actualHash ) - { - unlink( 'update.zip' ); - return false; - } - } - - /* aktualizacja bazy danych z manifestu */ - if ( !empty( $manifest['sql'] ) && is_array( $manifest['sql'] ) ) - { - foreach ( $manifest['sql'] as $query ) - { - $query = trim( $query ); - if ( $query ) - $mdb -> query( $query ); - } - } - - /* usuwanie plikow z manifestu */ - if ( !empty( $manifest['files']['deleted'] ) && is_array( $manifest['files']['deleted'] ) ) - { - foreach ( $manifest['files']['deleted'] as $filePath ) - { - $fullPath = '../' . $filePath; - if ( file_exists( $fullPath ) ) - unlink( $fullPath ); - } - } - - /* usuwanie katalogow z manifestu */ - if ( !empty( $manifest['directories_deleted'] ) && is_array( $manifest['directories_deleted'] ) ) - { - foreach ( $manifest['directories_deleted'] as $dirPath ) - { - $fullPath = '../' . $dirPath; - if ( is_dir( $fullPath ) ) - \S::delete_dir( $fullPath ); - } - } - } - else - { - /* legacy: aktualizacja bazy danych z _sql.txt */ - $sql = @file_get_contents( $baseUrl . '/ver_' . $ver . '_sql.txt' ); - if ( $sql ) - { - $sql = explode( PHP_EOL, $sql ); - if ( is_array( $sql ) ) foreach ( $sql as $query ) - { - $query = trim( $query ); - if ( $query ) - $mdb -> query( $query ); - } - } - - /* legacy: usuwanie zbednych plikow z _files.txt */ - $lines = @file_get_contents( $baseUrl . '/ver_' . $ver . '_files.txt' ); - if ( $lines ) - { - $lines = explode( PHP_EOL, $lines ); - if ( is_array( $lines ) ) foreach ( $lines as $line ) - { - if ( strpos( $line, 'F: ' ) !== false ) - { - $delFile = substr( $line, 3, strlen( $line ) ); - if ( file_exists( $delFile ) ) - unlink( $delFile ); - } - - if ( strpos( $line, 'D: ' ) !== false ) - { - $delDir = substr( $line, 3, strlen( $line ) ); - if ( is_dir( $delDir ) ) - \S::delete_dir( $delDir ); - } - } - } - } - - /* wgrywanie nowych plikow */ - $file_name = 'update.zip'; - - $path = pathinfo( realpath( $file_name ), PATHINFO_DIRNAME ); - $path = substr( $path, 0, strlen( $path ) - 5 ); - $zip = new \ZipArchive; - $res = $zip -> open( $file_name ); - if ( $res === TRUE ) - { - $zip -> extractTo( $path ); - $zip -> close(); - unlink( $file_name ); - } - - $updateThis = fopen( '../libraries/version.ini', 'w' ); - fwrite( $updateThis, $ver ); - fclose( $updateThis ); - - return true; - } - } + $repo = new \Domain\Releases\UpdateRepository($mdb, $settings); + return $repo->update(); } } diff --git a/autoload/class.Cron.php b/autoload/class.Cron.php index b219dee..72d30ab 100644 --- a/autoload/class.Cron.php +++ b/autoload/class.Cron.php @@ -4,511 +4,21 @@ class Cron public static function automatic_update_sites() { global $mdb; - - $results = $mdb -> query( "SELECT id, url FROM projects WHERE automatic_update = 1 AND DATE_ADD( last_update, INTERVAL 1 WEEK ) <= '" . date( 'Y-m-d H:i:s' ) . "'" ) -> fetchAll(); - if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row ) - { - $mdb -> delete( 'project_links_internal', [ 'AND' => [ 'project_id' => $row['id'], 'parent_id[!]' => null ] ] ); - $mdb -> delete( 'project_links_external', [ 'project_id' => $row['id'] ] ); - $mdb -> update( 'project_links_internal', [ 'visited' => 0 ], [ 'project_id' => $row['id'] ] ); - - $mdb -> update( 'projects', [ 'last_update' => date( 'Y-m-d H:i:s' ) ], [ 'id' => $row['id'] ] ); - - return [ 'status' => 'ok', 'msg' => 'Ponawiam sprawdzanie strony <a href="' . $row['url'] . '" target="_blank">' . $row['url'] . '</a>' ]; - } - return [ 'status' => 'empty' ]; + $repo = new \Domain\Cron\CronRepository($mdb); + return $repo->automaticUpdateSites(); } - + public static function get_site_main_links() { global $mdb; - - $results = $mdb -> query( 'SELECT id, url FROM projects WHERE id NOT IN ( SELECT project_id FROM project_links_internal GROUP BY project_id ) AND enabled = 1 LIMIT 1' ) -> fetchAll(); - if ( is_array( $results ) and !empty ( $results ) ) foreach ( $results as $row ) - { - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_URL, $row['url'] ); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); - curl_setopt( $ch, CURLOPT_VERBOSE, 1 ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 60 ); - curl_setopt( $ch, CURLOPT_HEADER, true ); - curl_setopt( $ch, CURLOPT_CAINFO, 'cacert.pem' ); - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); - curl_setopt( $ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.103 Safari/537.36' ); - $response = curl_exec( $ch ); - curl_close ( $ch ); - - if ( !curl_errno( $ch ) ) - { - $mdb -> insert( 'project_links_internal', [ - 'project_id' => $row['id'], - 'url' => $row['url'], - 'parent_id' => null - ] ); - - $doc = new DOMDocument; - $doc -> loadHTML( $response ); - foreach ( $doc -> getElementsByTagName( 'a' ) as $link ) - { - $url = $link -> getAttribute( 'href' ); - - if ( \S::is_url_internal( $row['url'], $url ) ) - { - if ( strpos( $url, '#' ) !== false ) - $url = rtrim( substr( $url, 0, strpos( $url, '#' ) ), '?,#' ); - - $url = \S::modify_internal_link( $row['url'], $url ); - - if ( !filter_var( $url, FILTER_VALIDATE_URL ) === false and !$mdb -> count( 'project_links_internal', [ 'AND' => [ 'project_id' => $row['id'], 'url' => $url ] ] ) ) - { - $mdb -> insert( 'project_links_internal', [ - 'project_id' => $row['id'], - 'url' => $url - ] ); - } - } - } - return [ 'status' => 'ok', 'msg' => 'Pobieram linki dla strony <a href="' . $row['url'] . '" target="_blank">' . $row['url'] . '</a>' ]; - } - else - return [ 'status' => 'ok', 'msg' => 'Błąd podczas pobierania strony <a href="' . $row['url'] . '" target="_blank">' . $row['url'] . '</a>' ]; - } - return [ 'status' => 'empty' ]; + $repo = new \Domain\Cron\CronRepository($mdb); + return $repo->getSiteMainLinks(); } - + public static function get_site_other_links() { global $mdb; - - $results = $mdb -> query( 'SELECT ' - . 'pli.id, project_id, pli.url, p.url AS project_url ' - . 'FROM ' - . 'project_links_internal AS pli ' - . 'INNER JOIN projects AS p ON p.id = pli.project_id ' - . 'WHERE ' - . 'visited = 0 AND enabled = 1 ' - . 'LIMIT 1' ) -> fetchAll(); - if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row ) - { - $url = parse_url( $row['url'] ); - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); - curl_setopt( $ch, CURLOPT_VERBOSE, 1 ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 60 ); - curl_setopt( $ch, CURLOPT_COOKIEFILE, 'temp/cookie.txt' ); - curl_setopt( $ch, CURLOPT_COOKIEJAR, 'temp/cookie.txt' ); - curl_setopt( $ch, CURLOPT_CAINFO, 'cacert.pem' ); - curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); - curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true ); - curl_setopt( $ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.103 Safari/537.36' ); - - curl_setopt( $ch, CURLOPT_URL, 'http://' . $url['host'] ); - $response = curl_exec( $ch ); - - curl_setopt( $ch, CURLOPT_URL, $row['url'] ); - $response = curl_exec( $ch ); - $content_type = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE ); - $code = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); - curl_close ( $ch ); - - if ( !curl_errno( $ch ) and ( $code == 200 or $code == 301 ) and strpos( $content_type, 'text/html' ) !== false ) - { - self::get_site_meta_title( $row['id'], $response ); - self::get_site_meta_keywords( $row['id'], $response ); - self::get_site_meta_description( $row['id'], $response ); - self::get_site_meta_robots( $row['id'], $response ); - self::get_site_meta_googlebot( $row['id'], $response ); - self::get_site_code_lenght( $row['id'], $response ); - self::get_site_text_lenght( $row['id'], $response ); - self::get_site_canonical( $row['id'], $response ); - self::get_table_exists( $row['id'], $response ); - self::get_iframe_exists( $row['id'], $response ); - self::get_h1_exists( $row['id'], $response ); - self::get_images_without_alt( $row['id'], $response ); - - /* pobranie linków ze strony */ - $doc = new DOMDocument; - $doc -> loadHTML( $response ); - - foreach ( $doc -> getElementsByTagName( 'a' ) as $link ) - { - $url = $link -> getAttribute( 'href' ); - - /* linki wewnętrzne na danej postronie */ - if ( \S::is_url_internal( $row['project_url'], $url ) ) - { - if ( strpos( $url, '#' ) !== false ) - $url = rtrim( substr( $url, 0, strpos( $url, '#' ) ), '?,#' ); - - $url = \S::modify_internal_link( $row['project_url'], $url, $row['url'] ); - $info = pathinfo( $url ); - - if ( !filter_var( $url, FILTER_VALIDATE_URL ) === false and !in_array( strtolower( $info['extension'] ), \S::not_html_format() ) and !$mdb -> count( 'project_links_internal', [ - 'AND' => [ - 'project_id' => $row['project_id'], - 'url' => $url - ] - ] ) ) - { - $mdb -> insert( 'project_links_internal', [ - 'project_id' => $row['project_id'], - 'url' => $url, - 'visited' => 0, - 'parent_id' => $row['id'], - 'response' => $response - ] ); - } - } - /* linki zewnętrzne na danej podstronie */ - else - { - $link -> getAttribute( 'rel' ) == 'nofollow' ? $nofollow = 1 : $nofollow = 0; - - $mdb -> insert( 'project_links_external', [ - 'project_id' => $row['project_id'], - 'link_id' => $row['id'], - 'url' => $link -> getAttribute( 'href' ), - 'nofollow' => $nofollow, - 'title' => $link -> getAttribute( 'title' ) - ] ); - } - } - - $mdb -> update( 'project_links_internal', [ - 'visited' => 1, - 'content_type' => $content_type, - 'response_code' => $code, - 'response' => $response - ], [ - 'id' => $row['id'] - ] ); - - return [ 'status' => 'ok', 'msg' => 'Pobieram informacje dla strony <a href="' . $row['url'] . '" target="_blank">' . $row['url'] . '</a>' ]; - } - else if ( $code == 404 or strpos( $content_type, 'text/html' ) === false ) - { - $mdb -> update( 'project_links_internal', [ - 'visited' => 1, - 'deleted' => 1, - 'content_type' => $content_type, - 'response_code' => $code - ], [ - 'id' => $row['id'] - ] ); - - return [ 'status' => 'ok', 'msg' => 'Pobieram informacje dla strony <a href="' . $row['url'] . '" target="_blank">' . $row['url'] . '</a>' ]; - } - else if ( $code !== 200 and strpos( $content_type, 'text/html' ) !== false ) - { - $mdb -> update( 'project_links_internal', [ - 'visited' => 1, - 'content_type' => $content_type, - 'response_code' => $code, - 'response' => $response - ], [ - 'id' => $row['id'] - ] ); - - return [ 'status' => 'ok', 'msg' => 'Pobieram informacje dla strony <a href="' . $row['url'] . '" target="_blank">' . $row['url'] . '</a>' ]; - } - else - return [ 'status' => 'ok', 'msg' => 'Błąd podczas pobierania strony <a href="' . $row['url'] . '" target="_blank">' . $row['url'] . '</a>' ]; - } - return [ 'status' => 'empty' ]; - } - - static public function get_images_without_alt( $url_id, $response ) - { - global $mdb; - - $doc = new DOMDocument; - $doc -> loadHTML( $response ); - $images = $doc -> getElementsByTagName("img"); - - $have_images_without_alt = 0; - foreach ( $images as $img ) - { - if ( !$img -> getAttribute( 'alt' ) ) - $have_images_without_alt = 1; - } - - $mdb -> update( 'project_links_internal', [ 'have_images_without_alt' => $have_images_without_alt ], [ 'id' => $url_id ] ); - } - - static public function get_table_exists( $url_id, $response ) - { - global $mdb; - - $doc = new DOMDocument; - $doc -> loadHTML( $response ); - $count = $doc -> getElementsByTagName("table"); - - $mdb -> update( 'project_links_internal', [ 'have_table' => $count -> length ? 1 : 0 ], [ 'id' => $url_id ] ); - } - - static public function get_iframe_exists( $url_id, $response ) - { - global $mdb; - - $doc = new DOMDocument; - $doc -> loadHTML( $response ); - $count = $doc -> getElementsByTagName("iframe"); - - $mdb -> update( 'project_links_internal', [ 'have_iframe' => $count -> length ? 1 : 0 ], [ 'id' => $url_id ] ); - } - - static public function get_h1_exists( $url_id, $response ) - { - global $mdb; - - $doc = new DOMDocument; - $doc -> loadHTML( $response ); - $count = $doc -> getElementsByTagName("h1"); - $mdb -> update( 'project_links_internal', [ 'have_h1' => $count -> length ? 1 : 0 ], [ 'id' => $url_id ] ); - } - - public static function get_site_meta_title( $url_id, $response ) - { - global $mdb; - - $title = ''; - - preg_match('/<title>([^>]*)<\/title>/si', $response, $match ); - - if ( isset( $match ) && is_array( $match ) && count( $match ) > 0 ) - $title = (string)strip_tags( $match[1] ); - - if ( !$title ) - { - preg_match_all('/<[\s]*meta[\s]*name="og:?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); - - if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) - { - $originals = $match[0]; - $names = $match[1]; - $values = $match[2]; - - if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) - { - $metaTags = array(); - for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) - { - $metaTags[ $names[$i] ] = array( - 'html' => htmlentities( $originals[$i] ), - 'value' => $values[$i] - ); - } - } - $title = (string)$metaTags['title']['value']; - } - } - - $mdb -> update( 'project_links_internal', [ 'title' => $title ], [ 'id' => $url_id ] ); - } - - public static function get_site_canonical( $url_id, $response ) - { - global $mdb; - - $doc = new DOMDocument; - $doc -> loadHTML( $response ); - foreach ( $doc -> getElementsByTagName( 'link' ) as $link ) - { - $rel = $link -> getAttribute( 'rel' ); - - if ( $rel == 'canonical' ) - { - $canonical = $link -> getAttribute( 'href' ); - } - } - - $mdb -> update( 'project_links_internal', [ 'canonical' => $canonical ], [ 'id' => $url_id ] ); - } - - public static function get_site_meta_keywords( $url_id, $response ) - { - global $mdb; - - $meta_keywords = ''; - - preg_match_all( '/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match ); - - if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) - { - $originals = $match[0]; - $names = $match[1]; - $values = $match[2]; - - if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) - { - $metaTags = array(); - for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) - { - $metaTags[ $names[$i] ] = array( - 'html' => htmlentities( $originals[$i] ), - 'value' => $values[$i] - ); - } - } - $meta_keywords = (string)$metaTags['keywords']['value']; - } - - if ( !$meta_keywords ) - { - preg_match_all( '/<[\s]*meta[\s]*property="og:?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match ); - - if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) - { - $originals = $match[0]; - $names = $match[1]; - $values = $match[2]; - - if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) - { - $metaTags = array(); - for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) - { - $metaTags[ $names[$i] ] = array( - 'html' => htmlentities( $originals[$i] ), - 'value' => $values[$i] - ); - } - } - $meta_keywords = (string)$metaTags['keywords']['value']; - } - } - - $mdb -> update( 'project_links_internal', [ 'meta_keywords' => $meta_keywords ], [ 'id' => $url_id ] ); - } - - public static function get_site_meta_description( $url_id, $response ) - { - global $mdb; - - $meta_description = ''; - - preg_match_all('/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); - - if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) - { - $originals = $match[0]; - $names = $match[1]; - $values = $match[2]; - - if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) - { - $metaTags = array(); - for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) - { - $metaTags[ $names[$i] ] = array( - 'html' => htmlentities( $originals[$i] ), - 'value' => $values[$i] - ); - } - } - $meta_description = (string)$metaTags['description']['value']; - } - - if ( !$meta_description ) - { - preg_match_all( '/<[\s]*meta[\s]*property="og:?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match ); - - if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) - { - $originals = $match[0]; - $names = $match[1]; - $values = $match[2]; - - if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) - { - $metaTags = array(); - for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) - { - $metaTags[ $names[$i] ] = array( - 'html' => htmlentities( $originals[$i] ), - 'value' => $values[$i] - ); - } - } - $meta_description = (string)$metaTags['description']['value']; - } - } - - $mdb -> update( 'project_links_internal', [ 'meta_description' => $meta_description ], [ 'id' => $url_id ] ); - } - - public static function get_site_meta_robots( $url_id, $response ) - { - global $mdb; - - $meta_robots = ''; - - preg_match_all('/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); - - if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) - { - $originals = $match[0]; - $names = $match[1]; - $values = $match[2]; - - if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) - { - $metaTags = array(); - for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) - { - $metaTags[ $names[$i] ] = array( - 'html' => htmlentities( $originals[$i] ), - 'value' => $values[$i] - ); - } - } - $meta_robots = (string)$metaTags['robots']['value']; - } - - $mdb -> update( 'project_links_internal', [ 'meta_robots' => $meta_robots ], [ 'id' => $url_id ] ); - } - - public static function get_site_meta_googlebot( $url_id, $response ) - { - global $mdb; - - $meta_googlebot = ''; - - preg_match_all('/<[\s]*meta[\s]*name="?' . '([^>"]*)"?[\s]*' . 'content="?([^>"]*)"?[\s]*[\/]?[\s]*>/si', $response, $match); - - if ( isset ( $match ) && is_array( $match ) && count( $match ) == 3 ) - { - $originals = $match[0]; - $names = $match[1]; - $values = $match[2]; - - if ( count( $originals ) == count( $names ) && count( $names ) == count( $values ) ) - { - $metaTags = array(); - for ( $i = 0, $limiti = count( $names ); $i < $limiti; $i++ ) - { - $metaTags[ $names[$i] ] = array( - 'html' => htmlentities( $originals[$i] ), - 'value' => $values[$i] - ); - } - } - $meta_googlebot = (string)$metaTags['googlebot']['value']; - } - - $mdb -> update( 'project_links_internal', [ 'meta_googlebot' => $meta_googlebot ], [ 'id' => $url_id ] ); - } - - public static function get_site_code_lenght( $url_id, $response ) - { - global $mdb; - $mdb -> update( 'project_links_internal', [ 'code_lenght' => strlen( $response ) ], [ 'id' => $url_id ] ); - } - - public static function get_site_text_lenght( $url_id, $response ) - { - global $mdb; - $mdb -> update( 'project_links_internal', [ 'text_lenght' => strlen( \S::strip_html_tags( $response ) ) ], [ 'id' => $url_id ] ); + $repo = new \Domain\Cron\CronRepository($mdb); + return $repo->getSiteOtherLinks(); } }