--- 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`