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
+
+
+
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 @@
+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 @@
+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 @@
+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 ' . $row['url'] . '' ];
- }
- 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 ' . $row['url'] . '' ];
- }
- else
- return [ 'status' => 'ok', 'msg' => 'Błąd podczas pobierania strony ' . $row['url'] . '' ];
- }
- 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 ' . $row['url'] . '' ];
- }
- 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 ' . $row['url'] . '' ];
- }
- 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 ' . $row['url'] . '' ];
- }
- else
- return [ 'status' => 'ok', 'msg' => 'Błąd podczas pobierania strony ' . $row['url'] . '' ];
- }
- 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>/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();
}
}