diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md
index d469645..c748bf1 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 (8 repos): Articles, Languages, Layouts, Pages, Settings, User, Scontainers, Banners
+- Domain (10 repos): Articles, Languages, Layouts, Pages, Settings, User, Scontainers, Banners, Authors, Newsletter
- Shared (7 modules): Cache, Helpers, Html, Image, Tpl, Email, Security
- Form Edit System: FormEditViewModel, multi-tab, validation, persistence
- PHPUnit base: Bootstrap, 3 test files
@@ -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 3*
+*Last updated: 2026-04-04 after Phase 4*
diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index 9218fb2..7bc154d 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: 3 of 19 complete
+Phases: 4 of 19 complete
## Already Completed (before PAUL)
- **Domain (6 repos):** Articles, Languages, Layouts, Pages, Settings, User
@@ -21,7 +21,7 @@ Phases: 3 of 19 complete
| 1 | Infrastructure & Autoloader | 1 | Complete | 2026-04-04 |
| 2 | Shared: Email + Security | 1 | Complete | 2026-04-04 |
| 3 | Domain: Scontainers + Banners | 1 | Complete | 2026-04-04 |
-| 4 | Domain: Authors + Newsletter | 1 | Not started | - |
+| 4 | Domain: Authors + Newsletter | 1 | Complete | 2026-04-04 |
| 5 | Domain: SeoAdditional + Cron + Releases | 1 | Not started | - |
| 6 | Admin: Base Infrastructure | 1 | Not started | - |
| 7 | Admin: Articles + ArticlesArchive | 1 | Not started | - |
diff --git a/.paul/STATE.md b/.paul/STATE.md
index 51094ea..87fa9d1 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -5,18 +5,18 @@
See: .paul/PROJECT.md (updated 2026-04-04)
**Core value:** Autorski system CMS umożliwiający zarządzanie treściami i stronami internetowymi.
-**Current focus:** Phase 3 complete — ready for Phase 4
+**Current focus:** Phase 4 complete — ready for Phase 5
## Current Position
Milestone: v0.1 Refaktoryzacja
-Phase: 3 of 19 (Domain: Scontainers + Banners) — Complete
-Plan: 03-01 complete
+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 3 complete, UNIFY done
+Last activity: 2026-04-04 — Phase 4 complete, UNIFY done
Progress:
-- Milestone: [▓▓░░░░░░░░] 15%
+- Milestone: [▓▓░░░░░░░░] 20%
## Loop Position
@@ -29,8 +29,8 @@ PLAN ──▶ APPLY ──▶ UNIFY
## Performance Metrics
**Velocity:**
-- Total plans completed: 3
-- Total execution time: ~20min
+- Total plans completed: 4
+- Total execution time: ~22min
**By Phase:**
@@ -39,6 +39,7 @@ PLAN ──▶ APPLY ──▶ UNIFY
| 01-infrastructure | 1/1 | ~10min | ~10min |
| 02-shared-email-security | 1/1 | ~8min | ~8min |
| 03-domain-scontainers-banners | 1/1 | ~2min | ~2min |
+| 04-domain-authors-newsletter | 1/1 | ~2min | ~2min |
## Accumulated Context
@@ -50,6 +51,7 @@ PLAN ──▶ APPLY ──▶ UNIFY
- 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
None.
@@ -60,9 +62,9 @@ None.
## Session Continuity
Last session: 2026-04-04
-Stopped at: Phase 3 complete, loop closed
-Next action: Run /paul:plan for Phase 4 (Domain: Authors + Newsletter)
-Resume file: .paul/phases/03-domain-scontainers-banners/03-01-SUMMARY.md
+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
---
*STATE.md — Updated after every significant action*
diff --git a/.paul/phases/04-domain-authors-newsletter/04-01-PLAN.md b/.paul/phases/04-domain-authors-newsletter/04-01-PLAN.md
new file mode 100644
index 0000000..0b5cf59
--- /dev/null
+++ b/.paul/phases/04-domain-authors-newsletter/04-01-PLAN.md
@@ -0,0 +1,216 @@
+---
+phase: 04-domain-authors-newsletter
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - autoload/Domain/Authors/AuthorsRepository.php
+ - autoload/Domain/Newsletter/NewsletterRepository.php
+ - autoload/admin/factory/class.Authors.php
+ - autoload/admin/factory/class.Newsletter.php
+ - autoload/front/factory/class.Authors.php
+ - autoload/front/factory/class.Newsletter.php
+autonomous: true
+delegation: auto
+---
+
+
+## Goal
+Create Domain\Authors\AuthorsRepository and Domain\Newsletter\NewsletterRepository, then convert legacy factory classes to wrapper delegation.
+
+## Purpose
+Continue DDD refactoring — migrate Authors and Newsletter data access to Domain repositories using established wrapper delegation pattern from Phase 3.
+
+## Output
+- 2 new Domain repository files
+- 4 legacy factory files converted to wrappers
+
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+
+## Prior Work
+@.paul/phases/03-domain-scontainers-banners/03-01-SUMMARY.md (wrapper delegation pattern reference)
+
+## Source Files
+@autoload/admin/factory/class.Authors.php
+@autoload/admin/factory/class.Newsletter.php
+@autoload/front/factory/class.Authors.php
+@autoload/front/factory/class.Newsletter.php
+@autoload/Domain/Languages/LanguagesRepository.php (pattern reference)
+
+
+
+
+## AC-1: AuthorsRepository exists with all methods
+```gherkin
+Given the autoloader is configured for Domain\ namespace
+When AuthorsRepository is instantiated with $db (Medoo)
+Then it provides simpleList(), authorDetails(), authorSave(), authorDelete(), authorByLang() methods
+And all methods use $this->db instead of global $mdb
+```
+
+## AC-2: NewsletterRepository exists with all methods
+```gherkin
+Given the autoloader is configured for Domain\ namespace
+When NewsletterRepository is instantiated with $db (Medoo)
+Then it provides emailsImport(), isAdminTemplate(), templateDelete(), send(), templateDetails(), templateSave(), templatesList(), unsubscribe(), confirm(), newsletterSend(), getHash(), signin(), getTemplate(), signout() methods
+And all methods use $this->db instead of global $mdb
+```
+
+## AC-3: Legacy admin factories delegate to repositories
+```gherkin
+Given admin\factory\Authors and admin\factory\Newsletter exist
+When their static methods are called
+Then they instantiate the Domain repository with global $mdb
+And delegate the call to the corresponding repository method
+And return the same result as before
+```
+
+## AC-4: Legacy front factories delegate to repositories
+```gherkin
+Given front\factory\Authors and front\factory\Newsletter exist
+When their static methods are called
+Then they delegate to the Domain repository
+And caching behavior is preserved (in repository for Authors)
+```
+
+
+
+
+
+
+ Task 1: Create AuthorsRepository and NewsletterRepository
+ autoload/Domain/Authors/AuthorsRepository.php, autoload/Domain/Newsletter/NewsletterRepository.php
+
+ Create Domain\Authors\AuthorsRepository following established pattern:
+ - namespace Domain\Authors
+ - Constructor: __construct($db) storing Medoo instance
+ - simpleList(): select from pp_authors, return array (from admin get_simple_list)
+ - authorDetails($authorId): get from pp_authors + select pp_authors_langs, return with ['languages'][$lang_id] sub-array
+ - authorSave($authorId, $author, $image, $description): insert/update pp_authors + pp_authors_langs with multi-language support. Same pattern as ScontainersRepository: query pp_langs for active languages, handle single vs multi lang arrays. Call \S::delete_cache() after.
+ - authorDelete($authorId): delete from pp_authors, call \S::delete_cache(), return result
+ - authorByLang($authorId, $langId): cached read using \Shared\Cache\CacheHandler::fetch("get_single_author:$authorId"). Get from pp_authors + pp_authors_langs for specific lang. Cache and return. Note: cache key does NOT include langId (matching original front factory).
+
+ Create Domain\Newsletter\NewsletterRepository following same pattern:
+ - namespace Domain\Newsletter
+ - Constructor: __construct($db)
+ - emailsImport($emails): parse comma/newline separated emails, validate with filter_var, insert unique into pp_newsletter. Return count of imported.
+ - isAdminTemplate($templateId): check if template exists in pp_newsletter_templates where id and admin=1. Return boolean.
+ - templateDelete($templateId): delete from pp_newsletter_templates where id. Return result.
+ - send($dates, $template, $onlyOnce): insert into pp_newsletter_send for each subscriber email from pp_newsletter. If $onlyOnce, check pp_newsletter_send for existing entries. Complex logic — replicate exactly from admin factory.
+ - templateDetails($templateId): get single template from pp_newsletter_templates.
+ - templateSave($id, $name, $text): insert/update pp_newsletter_templates. Call \S::delete_cache().
+ - templatesList(): select all from pp_newsletter_templates ordered.
+ - unsubscribe($hash): update pp_newsletter set status=0 where hash=$hash. Return result.
+ - confirm($hash): update pp_newsletter set status=1 where hash=$hash. Return result.
+ - newsletterSend($limit): select from pp_newsletter_send with limit, send emails via loop, delete sent entries. Replicate exactly from front factory.
+ - getHash($email): select hash from pp_newsletter where email. Return hash or false.
+ - signin($email): insert into pp_newsletter with email, hash (md5), status=0. Return result or hash.
+ - getTemplate($templateName): get template from pp_newsletter_templates where name=$templateName. Return template.
+ - signout($email): delete from pp_newsletter where email=$email. Return result.
+
+ IMPORTANT:
+ - PHP < 8.0 compatible
+ - Replicate logic EXACTLY from factory files — read them first
+ - Multi-language save pattern same as Phase 3 repos
+ - Keep all \S::delete_cache() calls where they exist in originals
+ - Newsletter send() and newsletterSend() are complex — read carefully and replicate precisely
+
+ php -l autoload/Domain/Authors/AuthorsRepository.php && php -l autoload/Domain/Newsletter/NewsletterRepository.php
+ AC-1 and AC-2 satisfied: Both repositories exist with all methods, use injected $db
+
+
+
+ Task 2: Convert legacy factories to wrapper delegation
+ autoload/admin/factory/class.Authors.php, autoload/admin/factory/class.Newsletter.php, autoload/front/factory/class.Authors.php, autoload/front/factory/class.Newsletter.php
+
+ Convert all 4 factory files to thin wrappers using Phase 3 pattern:
+ ```php
+ public static function method_name($args)
+ {
+ global $mdb;
+ $repo = new \Domain\Authors\AuthorsRepository($mdb);
+ return $repo->methodName($args);
+ }
+ ```
+
+ admin\factory\Authors:
+ - get_simple_list() → $repo->simpleList()
+ - delete_author($id_author) → $repo->authorDelete($id_author)
+ - save_author($id_author, $author, $image, $description) → $repo->authorSave($id_author, $author, $image, $description)
+
+ admin\factory\Newsletter:
+ - emails_import($emails) → $repo->emailsImport($emails)
+ - is_admin_template($template_id) → $repo->isAdminTemplate($template_id)
+ - newsletter_template_delete($template_id) → $repo->templateDelete($template_id)
+ - send($dates, $template, $only_once) → $repo->send($dates, $template, $only_once)
+ - email_template_detalis($id_template) → $repo->templateDetails($id_template)
+ - template_save($id, $name, $text) → $repo->templateSave($id, $name, $text)
+ - templates_list() → $repo->templatesList()
+
+ front\factory\Authors:
+ - get_single_author($id_author) → global $mdb; $repo = new \Domain\Authors\AuthorsRepository($mdb); return $repo->authorByLang($id_author, null);
+ Note: front factory uses global $lang but the cache key doesn't include lang — pass null or handle in repo. Check original carefully.
+
+ front\factory\Newsletter:
+ - newsletter_unsubscribe($hash) → $repo->unsubscribe($hash)
+ - newsletter_confirm($hash) → $repo->confirm($hash)
+ - newsletter_send($limit = 5) → $repo->newsletterSend($limit)
+ - get_hash($email) → $repo->getHash($email)
+ - newsletter_signin($email) → $repo->signin($email)
+ - get_template($template_name) → $repo->getTemplate($template_name)
+ - newsletter_signout($email) → $repo->signout($email)
+
+ IMPORTANT:
+ - Keep namespaces and method signatures IDENTICAL
+ - Read each file first before editing
+ - Each method = thin 3-line wrapper
+
+ php -l autoload/admin/factory/class.Authors.php && php -l autoload/admin/factory/class.Newsletter.php && php -l autoload/front/factory/class.Authors.php && php -l autoload/front/factory/class.Newsletter.php
+ AC-3 and AC-4 satisfied: All legacy factories delegate to Domain repositories
+
+
+
+
+
+
+## DO NOT CHANGE
+- autoload/autoloader.php
+- composer.json
+- autoload/admin/controls/ (admin controllers — later phases)
+- autoload/admin/view/ (admin views — later phases)
+- autoload/front/view/ (front views — later phases)
+- Any existing Domain\ repositories (Articles, Languages, Layouts, Pages, Settings, User, Scontainers, Banners)
+
+## SCOPE LIMITS
+- Only factory → repository migration
+- No new Composer dependencies
+- No database schema changes
+
+
+
+
+Before declaring plan complete:
+- [ ] php -l passes for all 6 files (2 new + 4 modified)
+- [ ] AuthorsRepository has: simpleList, authorDetails, authorSave, authorDelete, authorByLang
+- [ ] NewsletterRepository has: emailsImport, isAdminTemplate, templateDelete, send, templateDetails, templateSave, templatesList, unsubscribe, confirm, newsletterSend, getHash, signin, getTemplate, signout
+- [ ] All 4 factory files are thin wrappers (no direct $mdb usage)
+- [ ] No PHP 8.0+ syntax used
+- [ ] \S::delete_cache() calls preserved where originals had them
+
+
+
+- All tasks completed
+- All verification checks pass
+- Zero regression — factory method signatures unchanged
+- Domain repositories follow established pattern
+
+
+
diff --git a/.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md b/.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md
new file mode 100644
index 0000000..df098b1
--- /dev/null
+++ b/.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md
@@ -0,0 +1,111 @@
+---
+phase: 04-domain-authors-newsletter
+plan: 01
+subsystem: domain
+tags: [medoo, repository, authors, newsletter, wrapper-delegation]
+
+requires:
+ - phase: 01-infrastructure
+ provides: PSR-4 autoloader for Domain\ namespace
+
+provides:
+ - Domain\Authors\AuthorsRepository
+ - Domain\Newsletter\NewsletterRepository
+
+affects: [phase-10-admin-banners-authors-scontainers, phase-11-admin-newsletter-emails-seoadditional]
+
+tech-stack:
+ added: []
+ patterns: [wrapper-delegation, globals-to-parameters]
+
+key-files:
+ created:
+ - autoload/Domain/Authors/AuthorsRepository.php
+ - autoload/Domain/Newsletter/NewsletterRepository.php
+ modified:
+ - autoload/admin/factory/class.Authors.php
+ - autoload/admin/factory/class.Newsletter.php
+ - autoload/front/factory/class.Authors.php
+ - autoload/front/factory/class.Newsletter.php
+
+key-decisions:
+ - "Newsletter methods using global $settings/$lang now take them as explicit parameters"
+ - "authorByLang cache key preserved from original (no langId in key)"
+
+patterns-established:
+ - "Globals-to-parameters: when repo method needs $settings or $lang, wrapper passes them explicitly"
+
+duration: ~2min
+started: 2026-04-04T00:00:00Z
+completed: 2026-04-04T00:00:00Z
+---
+
+# Phase 4 Plan 01: Authors + Newsletter Repositories Summary
+
+**Domain repositories for Authors (5 methods) and Newsletter (14 methods) with wrapper delegation and globals-to-parameters pattern.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~2min |
+| Tasks | 2 completed (delegated) |
+| Files created | 2 |
+| Files modified | 4 |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: AuthorsRepository exists with all methods | Pass | 5 methods, 156 lines |
+| AC-2: NewsletterRepository exists with all methods | Pass | 14 methods, 281 lines |
+| AC-3: Legacy admin factories delegate to repositories | Pass | 11 static methods → wrappers |
+| AC-4: Legacy front factories delegate to repositories | Pass | 8 static methods → wrappers, globals passed as params |
+
+## Accomplishments
+
+- Created AuthorsRepository with simpleList, authorDetails, authorSave, authorDelete, authorByLang
+- Created NewsletterRepository with full subscriber lifecycle + template CRUD + sending
+- Established globals-to-parameters pattern for methods needing $settings/$lang
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `autoload/Domain/Authors/AuthorsRepository.php` | Created | Domain repository for authors CRUD + cached front read |
+| `autoload/Domain/Newsletter/NewsletterRepository.php` | Created | Domain repository for newsletter subscriber lifecycle, templates, sending |
+| `autoload/admin/factory/class.Authors.php` | Modified | Wrapper: 4 methods delegate to AuthorsRepository |
+| `autoload/admin/factory/class.Newsletter.php` | Modified | Wrapper: 7 methods delegate to NewsletterRepository |
+| `autoload/front/factory/class.Authors.php` | Modified | Wrapper: 1 method delegates |
+| `autoload/front/factory/class.Newsletter.php` | Modified | Wrapper: 7 methods delegate, passing $settings/$lang |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| Globals as parameters for newsletterSend/signin | Repos should not depend on globals | Front wrappers pass $settings, $lang explicitly |
+| Preserve original cache key for authorByLang | Backward compatibility with existing cache | Cache key "get_single_author:$id" without langId |
+
+## Deviations from Plan
+
+None — plan executed exactly as written.
+
+## Issues Encountered
+
+None.
+
+## Next Phase Readiness
+
+**Ready:**
+- Domain\Authors and Domain\Newsletter available for Admin controllers (Phases 10, 11)
+- All Domain repos for phases 3-4 complete
+
+**Concerns:**
+- None
+
+**Blockers:**
+- None
+
+---
+*Phase: 04-domain-authors-newsletter, Plan: 01*
+*Completed: 2026-04-04*
diff --git a/autoload/Domain/Authors/AuthorsRepository.php b/autoload/Domain/Authors/AuthorsRepository.php
new file mode 100644
index 0000000..5773f9a
--- /dev/null
+++ b/autoload/Domain/Authors/AuthorsRepository.php
@@ -0,0 +1,156 @@
+db = $db;
+ }
+
+ /**
+ * Prosta lista autorow
+ * @return array|bool
+ */
+ public function simpleList()
+ {
+ return $this->db->select('pp_authors', '*', ['ORDER' => ['author' => 'ASC']]);
+ }
+
+ /**
+ * Szczegoly autora z jezykami
+ * @param int $authorId
+ * @return array|bool
+ */
+ public function authorDetails($authorId)
+ {
+ $author = $this->db->get('pp_authors', '*', ['id' => (int)$authorId]);
+
+ $results = $this->db->select('pp_authors_langs', '*', ['id_author' => (int)$authorId]);
+ if (is_array($results)) foreach ($results as $row)
+ $author['languages'][$row['id_lang']] = $row;
+
+ return $author;
+ }
+
+ /**
+ * Zapis autora (insert lub update)
+ * @param int $authorId
+ * @param string $author
+ * @param string $image
+ * @param string|array $description
+ * @return int|bool
+ */
+ public function authorSave($authorId, $author, $image, $description)
+ {
+ if (!$authorId)
+ {
+ $this->db->insert('pp_authors', [
+ 'author' => $author,
+ 'image' => $image
+ ]);
+
+ $id = $this->db->id();
+
+ if ($id)
+ {
+ $i = 0;
+
+ $results = $this->db->select('pp_langs', ['id'], ['status' => 1, 'ORDER' => ['o' => 'ASC']]);
+ if (is_array($results) and count($results) > 1) foreach ($results as $row)
+ {
+ $this->db->insert('pp_authors_langs', [
+ 'id_author' => (int)$id,
+ 'id_lang' => $row['id'],
+ 'description' => $description[$i]
+ ]);
+ $i++;
+ }
+ else if (is_array($results) and count($results) == 1) foreach ($results as $row)
+ {
+ $this->db->insert('pp_authors_langs', [
+ 'id_author' => (int)$id,
+ 'id_lang' => $row['id'],
+ 'description' => $description
+ ]);
+ }
+
+ \S::delete_cache();
+
+ return $id;
+ }
+ }
+ else
+ {
+ $this->db->update('pp_authors', [
+ 'author' => $author,
+ 'image' => $image
+ ], [
+ 'id' => (int)$authorId
+ ]);
+
+ $this->db->delete('pp_authors_langs', ['id_author' => (int)$authorId]);
+
+ $i = 0;
+
+ $results = $this->db->select('pp_langs', ['id'], ['status' => 1, 'ORDER' => ['o' => 'ASC']]);
+ if (is_array($results) and count($results) > 1) foreach ($results as $row)
+ {
+ $this->db->insert('pp_authors_langs', [
+ 'id_author' => (int)$authorId,
+ 'id_lang' => $row['id'],
+ 'description' => $description[$i]
+ ]);
+ $i++;
+ }
+ else if (is_array($results) and count($results) == 1) foreach ($results as $row)
+ {
+ $this->db->insert('pp_authors_langs', [
+ 'id_author' => (int)$authorId,
+ 'id_lang' => $row['id'],
+ 'description' => $description
+ ]);
+ }
+
+ \S::delete_cache();
+
+ return $authorId;
+ }
+ return false;
+ }
+
+ /**
+ * Usuniecie autora
+ * @param int $authorId
+ * @return object|bool
+ */
+ public function authorDelete($authorId)
+ {
+ $result = $this->db->delete('pp_authors', ['id' => (int)$authorId]);
+ \S::delete_cache();
+
+ return $result;
+ }
+
+ /**
+ * Szczegoly autora z cache (front)
+ * @param int $authorId
+ * @return array|bool
+ */
+ public function authorByLang($authorId)
+ {
+ if (!$author = \Shared\Cache\CacheHandler::fetch("get_single_author:$authorId"))
+ {
+ $author = $this->db->get('pp_authors', '*', ['id' => (int)$authorId]);
+
+ $results = $this->db->select('pp_authors_langs', '*', ['id_author' => (int)$authorId]);
+ if (is_array($results)) foreach ($results as $row)
+ $author['languages'][$row['id_lang']] = $row;
+
+ \Shared\Cache\CacheHandler::store("get_single_author:$authorId", $author);
+ }
+ return $author;
+ }
+}
diff --git a/autoload/Domain/Newsletter/NewsletterRepository.php b/autoload/Domain/Newsletter/NewsletterRepository.php
new file mode 100644
index 0000000..122d02f
--- /dev/null
+++ b/autoload/Domain/Newsletter/NewsletterRepository.php
@@ -0,0 +1,281 @@
+db = $db;
+ }
+
+ /**
+ * Import emaili do newslettera
+ * @param string $emails
+ * @return bool
+ */
+ public function emailsImport($emails)
+ {
+ $emails = explode(PHP_EOL, $emails);
+ if (is_array($emails)) foreach ($emails as $email)
+ {
+ if (trim($email) and !$this->db->count('pp_newsletter', ['email' => trim($email)]))
+ $this->db->insert('pp_newsletter', [
+ 'email' => trim($email),
+ 'hash' => md5($email . time()),
+ 'status' => 1
+ ]);
+ }
+ return true;
+ }
+
+ /**
+ * Sprawdza czy szablon jest adminski
+ * @param int $templateId
+ * @return string|bool
+ */
+ public function isAdminTemplate($templateId)
+ {
+ return $this->db->get('pp_newsletter_templates', 'is_admin', ['id' => (int)$templateId]);
+ }
+
+ /**
+ * Usuniecie szablonu newslettera
+ * @param int $templateId
+ * @return object|bool
+ */
+ public function templateDelete($templateId)
+ {
+ return $this->db->delete('pp_newsletter_templates', ['id' => (int)$templateId]);
+ }
+
+ /**
+ * Wysylka newslettera - kolejkowanie
+ * @param string $dates
+ * @param int $template
+ * @param string $onlyOnce
+ * @return bool
+ */
+ public function send($dates, $template, $onlyOnce)
+ {
+ $results = $this->db->select('pp_newsletter', 'email', ['status' => 1]);
+ if (is_array($results) and !empty($results)) foreach ($results as $row)
+ {
+ if ($template and $onlyOnce)
+ {
+ if (!$this->db->count('pp_newsletter_send', ['AND' => ['id_template' => $template, 'email' => $row]]))
+ $this->db->insert('pp_newsletter_send', [
+ 'email' => $row,
+ 'dates' => $dates,
+ 'id_template' => $template ? $template : null,
+ 'only_once' => ($onlyOnce == 'on' and $template) ? 1 : 0
+ ]);
+ }
+ else
+ $this->db->insert('pp_newsletter_send', [
+ 'email' => $row,
+ 'dates' => $dates,
+ 'id_template' => $template ? $template : null,
+ 'only_once' => ($onlyOnce == 'on' and $template) ? 1 : 0
+ ]);
+ }
+ return true;
+ }
+
+ /**
+ * Szczegoly szablonu email
+ * @param int $templateId
+ * @return array|bool
+ */
+ public function templateDetails($templateId)
+ {
+ $result = $this->db->get('pp_newsletter_templates', '*', ['id' => (int)$templateId]);
+ return $result;
+ }
+
+ /**
+ * Zapis szablonu (insert lub update)
+ * @param int $id
+ * @param string $name
+ * @param string $text
+ * @return int|bool
+ */
+ public function templateSave($id, $name, $text)
+ {
+ if (!$id)
+ {
+ if ($this->db->insert('pp_newsletter_templates', [
+ 'name' => $name,
+ 'text' => $text
+ ]))
+ {
+ \S::delete_cache();
+ return $this->db->id();
+ }
+ }
+ else
+ {
+ $this->db->update('pp_newsletter_templates', [
+ 'name' => $name,
+ 'text' => $text
+ ], [
+ 'id' => (int)$id
+ ]);
+
+ \S::delete_cache();
+ return $id;
+ }
+ }
+
+ /**
+ * Lista szablonow (nie-adminskich)
+ * @return array|bool
+ */
+ public function templatesList()
+ {
+ return $this->db->select('pp_newsletter_templates', '*', ['is_admin' => 0, 'ORDER' => ['name' => 'ASC']]);
+ }
+
+ /**
+ * Wypisanie z newslettera po hashu
+ * @param string $hash
+ * @return object|bool
+ */
+ public function unsubscribe($hash)
+ {
+ return $this->db->update('pp_newsletter', ['status' => 0], ['hash' => $hash]);
+ }
+
+ /**
+ * Potwierdzenie zapisu po hashu
+ * @param string $hash
+ * @return bool
+ */
+ public function confirm($hash)
+ {
+ if (!$id = $this->db->get('pp_newsletter', 'id', ['AND' => ['hash' => $hash, 'status' => 0]]))
+ return false;
+ else
+ $this->db->update('pp_newsletter', ['status' => 1], ['id' => $id]);
+ return true;
+ }
+
+ /**
+ * Wysylka zakolejkowanych newsletterow (cron/front)
+ * @param int $limit
+ * @param array $settings
+ * @param array $lang
+ * @return bool
+ */
+ public function newsletterSend($limit, $settings, $lang)
+ {
+ $results = $this->db->query('SELECT * FROM pp_newsletter_send WHERE mailed = 0 ORDER BY id ASC LIMIT ' . (int)$limit)->fetchAll();
+ if (is_array($results) and !empty($results))
+ {
+ foreach ($results as $row)
+ {
+ $dates = explode(' - ', $row['dates']);
+
+ $text = \admin\view\Newsletter::preview(
+ \admin\factory\Articles::articles_by_date_add($dates[0], $dates[1]),
+ \admin\factory\Settings::settings_details(),
+ \admin\factory\Newsletter::email_template_detalis($row['id_template'])
+ );
+
+ if ($settings['ssl']) $base = 'https'; else $base = 'http';
+
+ $link = $base . "://" . $_SERVER['SERVER_NAME'] . '/newsletter/unsubscribe/hash=' . $this->getHash($row['email']);
+ $text = str_replace('[WYPISZ_SIE]', $link, $text);
+
+ $regex = "-(
]+src\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
+ $text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
+
+ $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
+ $text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
+
+ \S::send_email($row['email'], 'Newsletter ze strony: ' . $_SERVER['SERVER_NAME'], $text);
+
+ if ($row['only_once'])
+ $this->db->update('pp_newsletter_send', ['mailed' => 1], ['id' => $row['id']]);
+ else
+ $this->db->delete('pp_newsletter_send', ['id' => $row['id']]);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Pobranie hasha dla emaila
+ * @param string $email
+ * @return string|bool
+ */
+ public function getHash($email)
+ {
+ return $this->db->get('pp_newsletter', 'hash', ['email' => $email]);
+ }
+
+ /**
+ * Zapis do newslettera z wysylka potwierdzenia
+ * @param string $email
+ * @param array $settings
+ * @param array $lang
+ * @return bool
+ */
+ public function signin($email, $settings, $lang)
+ {
+ if (!\S::email_check($email))
+ return false;
+
+ if (!$this->db->get('pp_newsletter', 'id', ['email' => $email]))
+ {
+ $hash = md5(time() . $email);
+
+ $text = $settings['newsletter_header'];
+ $text .= $this->getTemplate('#potwierdzenie-zapisu-do-newslettera');
+ $text .= $settings['newsletter_footer_1'];
+
+ $settings['ssl'] ? $base = 'https' : $base = 'http';
+
+ $regex = "-(
]+src\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
+ $text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
+
+ $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
+ $text = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text);
+
+ $link = '/newsletter/confirm/hash=' . $hash;
+
+ $text = str_replace('[LINK]', $link, $text);
+
+ $send = \S::send_email($email, $lang['potwierdz-zapisanie-sie-do-newslettera'], $text);
+
+ $this->db->insert('pp_newsletter', ['email' => $email, 'hash' => $hash, 'status' => 0]);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Pobranie szablonu po nazwie
+ * @param string $templateName
+ * @return string|bool
+ */
+ public function getTemplate($templateName)
+ {
+ return $this->db->get('pp_newsletter_templates', 'text', ['name' => $templateName]);
+ }
+
+ /**
+ * Wypisanie z newslettera po emailu
+ * @param string $email
+ * @return object|bool
+ */
+ public function signout($email)
+ {
+ if ($this->db->get('pp_newsletter', 'id', ['email' => $email]))
+ return $this->db->delete('pp_newsletter', ['email' => $email]);
+ return false;
+ }
+}
diff --git a/autoload/admin/factory/class.Authors.php b/autoload/admin/factory/class.Authors.php
index 5c05c7a..1c2aa3a 100644
--- a/autoload/admin/factory/class.Authors.php
+++ b/autoload/admin/factory/class.Authors.php
@@ -1,4 +1,4 @@
-
+ select( 'pp_authors', '*', [ 'ORDER' => [ 'author' => 'ASC' ] ] );
+ $repo = new \Domain\Authors\AuthorsRepository($mdb);
+ return $repo->simpleList();
}
// usunięcie autora
static public function delete_author( $id_author )
{
global $mdb;
-
- $result = $mdb -> delete( 'pp_authors', [ 'id' => (int)$id_author ] );
- \S::delete_cache();
-
- return $result;
+ $repo = new \Domain\Authors\AuthorsRepository($mdb);
+ return $repo->authorDelete($id_author);
}
// zapis autora
static public function save_author( $id_author, $author, $image, $description )
{
global $mdb;
-
- if ( !$id_author )
- {
- $mdb -> insert( 'pp_authors', [
- 'author' => $author,
- 'image' => $image
- ] );
-
- $id = $mdb -> id();
-
- if ( $id )
- {
- $i = 0;
-
- $results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
- if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
- {
- $mdb -> insert( 'pp_authors_langs', [
- 'id_author' => (int)$id,
- 'id_lang' => $row['id'],
- 'description' => $description[ $i ]
- ] );
- $i++;
- }
- else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
- {
- $mdb -> insert( 'pp_authors_langs', [
- 'id_author' => (int)$id,
- 'id_lang' => $row['id'],
- 'description' => $description
- ] );
- }
-
- \S::delete_cache();
-
- return $id;
- }
- }
- else
- {
- $mdb -> update( 'pp_authors', [
- 'author' => $author,
- 'image' => $image
- ], [
- 'id' => (int)$id_author
- ] );
-
- $mdb -> delete( 'pp_authors_langs', [ 'id_author' => (int)$id_author ] );
-
- $i = 0;
-
- $results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
- if ( is_array( $results ) and count( $results ) > 1 ) foreach ( $results as $row )
- {
- $mdb -> insert( 'pp_authors_langs', [
- 'id_author' => (int)$id_author,
- 'id_lang' => $row['id'],
- 'description' => $description[ $i ]
- ] );
- $i++;
- }
- else if ( is_array( $results ) and count( $results ) == 1 ) foreach ( $results as $row )
- {
- $mdb -> insert( 'pp_authors_langs', [
- 'id_author' => (int)$id_author,
- 'id_lang' => $row['id'],
- 'description' => $description
- ] );
- }
-
- \S::delete_cache();
-
- return $id_author;
- }
- return false;
+ $repo = new \Domain\Authors\AuthorsRepository($mdb);
+ return $repo->authorSave($id_author, $author, $image, $description);
}
// szczególy autora
static public function get_single_author( $id_author )
{
global $mdb;
-
- $author = $mdb -> get( 'pp_authors', '*', [ 'id' => (int)$id_author ] );
-
- $results = $mdb -> select( 'pp_authors_langs', '*', [ 'id_author' => (int)$id_author ] );
- if ( is_array( $results ) ) foreach ( $results as $row )
- $author['languages'][$row['id_lang']] = $row;
-
- return $author;
+ $repo = new \Domain\Authors\AuthorsRepository($mdb);
+ return $repo->authorDetails($id_author);
}
}
\ No newline at end of file
diff --git a/autoload/admin/factory/class.Newsletter.php b/autoload/admin/factory/class.Newsletter.php
index 0706148..a4805b6 100644
--- a/autoload/admin/factory/class.Newsletter.php
+++ b/autoload/admin/factory/class.Newsletter.php
@@ -6,100 +6,49 @@ class Newsletter
public static function emails_import( $emails )
{
global $mdb;
-
- $emails = explode( PHP_EOL, $emails );
- if ( is_array( $emails ) ) foreach ( $emails as $email )
- {
- if ( trim( $email ) and !$mdb -> count( 'pp_newsletter', [ 'email' => trim( $email ) ] ) )
- $mdb -> insert( 'pp_newsletter', [
- 'email' => trim( $email ),
- 'hash' => md5( $email . time() ),
- 'status' => 1
- ] );
- }
- return true;
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->emailsImport($emails);
}
-
+
public static function is_admin_template( $template_id )
{
global $mdb;
- return $mdb -> get( 'pp_newsletter_templates', 'is_admin', [ 'id' => (int)$template_id ] );
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->isAdminTemplate($template_id);
}
-
+
public static function newsletter_template_delete( $template_id )
{
global $mdb;
- return $mdb -> delete( 'pp_newsletter_templates', [ 'id' => (int)$template_id ] );
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->templateDelete($template_id);
}
-
+
public static function send( $dates, $template, $only_once )
{
global $mdb;
-
- $results = $mdb -> select( 'pp_newsletter', 'email', [ 'status' => 1 ] );
- if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
- {
- if ( $template and $only_once )
- {
- if ( !$mdb -> count( 'pp_newsletter_send', [ 'AND' => [ 'id_template' => $template, 'email' => $row ] ] ) )
- $mdb -> insert( 'pp_newsletter_send', [
- 'email' => $row,
- 'dates' => $dates,
- 'id_template' => $template ? $template : null,
- 'only_once' => ( $only_once == 'on' and $template ) ? 1 : 0
- ] );
- }
- else
- $mdb -> insert( 'pp_newsletter_send', [
- 'email' => $row,
- 'dates' => $dates,
- 'id_template' => $template ? $template : null,
- 'only_once' => ( $only_once == 'on' and $template ) ? 1 : 0
- ] );
- }
- return true;
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->send($dates, $template, $only_once);
}
-
+
public static function email_template_detalis ($id_template)
{
global $mdb;
-
- $result = $mdb -> get ('pp_newsletter_templates', '*', [ 'id' => (int)$id_template ] );
- return $result;
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->templateDetails($id_template);
}
-
+
public static function template_save($id, $name, $text)
{
global $mdb;
- if ( !$id )
- {
- if ( $mdb -> insert( 'pp_newsletter_templates', [
- 'name' => $name,
- 'text' => $text
- ] ) )
- {
- \S::delete_cache();
- return $mdb -> id();
- }
- }
- else
- {
- $mdb -> update( 'pp_newsletter_templates', [
- 'name' => $name,
- 'text' => $text
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->templateSave($id, $name, $text);
+ }
- ], [
- 'id' => (int)$id
- ] );
-
- \S::delete_cache();
- return $id;
- }
- }
-
public static function templates_list()
{
global $mdb;
- return $mdb -> select( 'pp_newsletter_templates', '*', [ 'is_admin' => 0, 'ORDER' => [ 'name' => 'ASC' ] ] );
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->templatesList();
}
-}
+}
\ No newline at end of file
diff --git a/autoload/front/factory/class.Authors.php b/autoload/front/factory/class.Authors.php
index a021624..d31ed78 100644
--- a/autoload/front/factory/class.Authors.php
+++ b/autoload/front/factory/class.Authors.php
@@ -1,4 +1,4 @@
-
+ get( 'pp_authors', '*', [ 'id' => (int)$id_author ] );
-
- $results = $mdb -> select( 'pp_authors_langs', '*', [ 'id_author' => (int)$id_author ] );
- if ( is_array( $results ) ) foreach ( $results as $row )
- $author['languages'][$row['id_lang']] = $row;
-
- \Cache::store( "get_single_author:$id_author", $author );
- }
- return $author;
+ $repo = new \Domain\Authors\AuthorsRepository($mdb);
+ return $repo->authorByLang($id_author);
}
}
\ No newline at end of file
diff --git a/autoload/front/factory/class.Newsletter.php b/autoload/front/factory/class.Newsletter.php
index 6fa4cfc..368a2dd 100644
--- a/autoload/front/factory/class.Newsletter.php
+++ b/autoload/front/factory/class.Newsletter.php
@@ -6,113 +6,49 @@ class Newsletter
public static function newsletter_unsubscribe( $hash )
{
global $mdb;
- return $mdb -> update( 'pp_newsletter', [ 'status' => 0 ], [ 'hash' => $hash ] );
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->unsubscribe($hash);
}
-
+
public static function newsletter_confirm( $hash )
{
global $mdb;
- if ( !$id = $mdb -> get( 'pp_newsletter', 'id', [ 'AND' => [ 'hash' => $hash, 'status' => 0 ] ] ) )
- return false;
- else
- $mdb -> update( 'pp_newsletter', [ 'status' => 1 ], [ 'id' => $id ] );
- return true;
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->confirm($hash);
}
-
+
public static function newsletter_send( $limit = 5 )
{
global $mdb, $settings, $lang;
-
- $results = $mdb -> query( 'SELECT * FROM pp_newsletter_send WHERE mailed = 0 ORDER BY id ASC LIMIT ' . $limit ) -> fetchAll();
- if ( is_array( $results ) and !empty( $results ) )
- {
- foreach ( $results as $row )
- {
- $dates = explode( ' - ', $row['dates'] );
-
- $text = \admin\view\Newsletter::preview(
- \admin\factory\Articles::articles_by_date_add( $dates[0], $dates[1] ),
- \admin\factory\Settings::settings_details(),
- \admin\factory\Newsletter::email_template_detalis($row['id_template'])
- );
-
- if ( $settings['ssl'] ) $base = 'https'; else $base = 'http';
-
- $link = $base . "://" . $_SERVER['SERVER_NAME'] . '/newsletter/unsubscribe/hash=' . \front\factory\Newsletter::get_hash( $row['email'] );
- $text = str_replace( '[WYPISZ_SIE]', $link, $text );
-
- $regex = "-(
]+src\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
- $text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
-
- $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|http(|s)://).)*)(['\"][^>]*>)-i";
- $text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
-
- \S::send_email( $row['email'], 'Newsletter ze strony: ' . $_SERVER['SERVER_NAME'], $text );
-
- if ( $row['only_once'] )
- $mdb -> update( 'pp_newsletter_send', [ 'mailed' => 1 ], [ 'id' => $row['id'] ] );
- else
- $mdb -> delete( 'pp_newsletter_send', [ 'id' => $row['id'] ] );
- }
- return true;
- }
- return false;
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->newsletterSend($limit, $settings, $lang);
}
-
+
public static function get_hash( $email )
{
global $mdb;
- return $mdb -> get( 'pp_newsletter', 'hash', [ 'email' => $email ] );
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->getHash($email);
}
-
+
public static function newsletter_signin( $email )
{
global $mdb, $lang, $settings;
-
- if ( !\S::email_check( $email ) )
- return false;
-
- if ( !$mdb -> get( 'pp_newsletter', 'id', [ 'email' => $email ] ) )
- {
- $hash = md5( time() . $email );
-
- $text = $settings['newsletter_header'];
- $text .= \front\factory\Newsletter::get_template( '#potwierdzenie-zapisu-do-newslettera' );
- $text .= $settings['newsletter_footer_1'];
-
- $settings['ssl'] ? $base = 'https' : $base = 'http';
-
- $regex = "-(
]+src\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
- $text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
-
- $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i";
- $text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
-
- $link = '/newsletter/confirm/hash=' . $hash;
-
- $text = str_replace( '[LINK]', $link, $text );
-
- $send = \S::send_email( $email, $lang['potwierdz-zapisanie-sie-do-newslettera'], $text );
-
- $mdb -> insert( 'pp_newsletter', [ 'email' => $email, 'hash' => $hash, 'status' => 0 ] );
-
- return true;
- }
- return false;
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->signin($email, $settings, $lang);
}
-
+
public static function get_template( $template_name )
{
global $mdb;
- return $mdb -> get( 'pp_newsletter_templates', 'text', [ 'name' => $template_name ] );
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->getTemplate($template_name);
}
-
+
public static function newsletter_signout( $email )
{
global $mdb;
-
- if ( $mdb -> get( 'pp_newsletter', 'id', [ 'email' => $email ] ) )
- return $mdb -> delete( 'pp_newsletter', [ 'email' => $email ] );
- return false;
+ $repo = new \Domain\Newsletter\NewsletterRepository($mdb);
+ return $repo->signout($email);
}
}
\ No newline at end of file