From ffe661b4d227d02ed357ae2a957c4006739527da Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Sat, 4 Apr 2026 18:21:32 +0200 Subject: [PATCH] feat(domain): Domain\Authors + Domain\Newsletter repositories z wrapper delegation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 complete: - AuthorsRepository: simpleList, authorDetails, authorSave, authorDelete, authorByLang - NewsletterRepository: 14 methods — subscriber lifecycle, templates, sending - 4 legacy factories converted to thin wrappers - Globals ($settings, $lang) passed as explicit params to repo methods Co-Authored-By: Claude Opus 4.6 (1M context) --- .paul/PROJECT.md | 4 +- .paul/ROADMAP.md | 4 +- .paul/STATE.md | 22 +- .../04-01-PLAN.md | 216 ++++++++++++++ .../04-01-SUMMARY.md | 111 +++++++ autoload/Domain/Authors/AuthorsRepository.php | 156 ++++++++++ .../Newsletter/NewsletterRepository.php | 281 ++++++++++++++++++ autoload/admin/factory/class.Authors.php | 99 +----- autoload/admin/factory/class.Newsletter.php | 93 ++---- autoload/front/factory/class.Authors.php | 16 +- autoload/front/factory/class.Newsletter.php | 104 ++----- 11 files changed, 833 insertions(+), 273 deletions(-) create mode 100644 .paul/phases/04-domain-authors-newsletter/04-01-PLAN.md create mode 100644 .paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md create mode 100644 autoload/Domain/Authors/AuthorsRepository.php create mode 100644 autoload/Domain/Newsletter/NewsletterRepository.php 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 + + + +After completion, create `.paul/phases/04-domain-authors-newsletter/04-01-SUMMARY.md` + 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