diff --git a/admin/templates/settings/settings.php b/admin/templates/settings/settings.php
index 37274e3..a8cbde8 100644
--- a/admin/templates/settings/settings.php
+++ b/admin/templates/settings/settings.php
@@ -143,7 +143,7 @@ ob_start();
if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
if ( $lg['status'] ):?>
- - if ( $lg['id'] == \front\factory\Languages::default_language() ) echo ' ';?>= $lg['name'];?>
+ - if ( $lg['id'] == ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() ) echo ' ';?>= $lg['name'];?>
endif;?>
endforeach; endif;?>
diff --git a/admin/templates/shop-category/category-edit.php b/admin/templates/shop-category/category-edit.php
index 9053b6c..29641ad 100644
--- a/admin/templates/shop-category/category-edit.php
+++ b/admin/templates/shop-category/category-edit.php
@@ -17,7 +17,7 @@ ob_start();
if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
if ( $lg['status'] ):?>
- - if ( $lg['id'] == \front\factory\Languages::default_language() ) echo ' ';?>= $lg['name'];?>
+ - if ( $lg['id'] == $this->dlang ) echo ' ';?>= $lg['name'];?>
endif;?>
endforeach; endif;?>
@@ -104,7 +104,7 @@ ob_start();
if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
if ( $lg['status'] ):?>
- - if ( $lg['id'] == \front\factory\Languages::default_language() ) echo ' ';?>= $lg['name'];?>
+ - if ( $lg['id'] == $this->dlang ) echo ' ';?>= $lg['name'];?>
endif;?>
endforeach; endif;?>
diff --git a/ajax.php b/ajax.php
index 11429ad..d1d74c0 100644
--- a/ajax.php
+++ b/ajax.php
@@ -34,15 +34,17 @@ $mdb = new medoo( [
'charset' => 'utf8'
] );
+$langRepo = new \Domain\Languages\LanguagesRepository( $mdb );
+
if ( !$lang_id = \S::get_session( 'current-lang' ) )
{
- $lang_id = \front\factory\Languages::default_language();
+ $lang_id = $langRepo->defaultLanguage();
\S::set_session( 'current-lang', $lang_id );
}
if ( !$lang = \S::get_session( 'lang' ) )
{
- $lang = \front\factory\Languages::lang_translations();
+ $lang = $langRepo->translations();
\S::set_session( 'lang', $lang );
}
diff --git a/autoload/Domain/Newsletter/NewsletterRepository.php b/autoload/Domain/Newsletter/NewsletterRepository.php
index cdf7da9..dca40b1 100644
--- a/autoload/Domain/Newsletter/NewsletterRepository.php
+++ b/autoload/Domain/Newsletter/NewsletterRepository.php
@@ -2,6 +2,7 @@
namespace Domain\Newsletter;
use Domain\Settings\SettingsRepository;
+use Domain\Article\ArticleRepository;
class NewsletterRepository
{
@@ -9,11 +10,29 @@ class NewsletterRepository
private $db;
private SettingsRepository $settingsRepository;
+ private ?ArticleRepository $articleRepository;
+ private ?NewsletterPreviewRenderer $previewRenderer;
- public function __construct($db, ?SettingsRepository $settingsRepository = null)
- {
+ public function __construct(
+ $db,
+ ?SettingsRepository $settingsRepository = null,
+ ?ArticleRepository $articleRepository = null,
+ ?NewsletterPreviewRenderer $previewRenderer = null
+ ) {
$this->db = $db;
$this->settingsRepository = $settingsRepository ?? new SettingsRepository($db);
+ $this->articleRepository = $articleRepository;
+ $this->previewRenderer = $previewRenderer;
+ }
+
+ private function getArticleRepository(): ArticleRepository
+ {
+ return $this->articleRepository ?? ($this->articleRepository = new ArticleRepository($this->db));
+ }
+
+ private function getPreviewRenderer(): NewsletterPreviewRenderer
+ {
+ return $this->previewRenderer ?? ($this->previewRenderer = new NewsletterPreviewRenderer());
}
public function getSettings(): array
@@ -289,4 +308,138 @@ class NewsletterRepository
'total' => $total,
];
}
+
+ // ── Frontend methods ──
+
+ public function unsubscribe(string $hash): bool
+ {
+ $id = $this->db->get('pp_newsletter', 'id', ['hash' => $hash]);
+ if (!$id) {
+ return false;
+ }
+
+ $this->db->delete('pp_newsletter', ['id' => $id]);
+ return true;
+ }
+
+ public function confirmSubscription(string $hash): bool
+ {
+ $id = $this->db->get('pp_newsletter', 'id', ['AND' => ['hash' => $hash, 'status' => 0]]);
+ if (!$id) {
+ return false;
+ }
+
+ $this->db->update('pp_newsletter', ['status' => 1], ['id' => $id]);
+ return true;
+ }
+
+ public function getHashByEmail(string $email): ?string
+ {
+ $hash = $this->db->get('pp_newsletter', 'hash', ['email' => $email]);
+ return $hash ? (string)$hash : null;
+ }
+
+ public function removeByEmail(string $email): bool
+ {
+ if (!$this->db->get('pp_newsletter', 'id', ['email' => $email])) {
+ return false;
+ }
+
+ return (bool)$this->db->delete('pp_newsletter', ['email' => $email]);
+ }
+
+ public function signup(string $email, string $serverName, bool $ssl, array $settings): bool
+ {
+ if (!\S::email_check($email)) {
+ return false;
+ }
+
+ if ($this->db->get('pp_newsletter', 'id', ['email' => $email])) {
+ return false;
+ }
+
+ $hash = md5(time() . $email);
+
+ $text = ($settings['newsletter_header'] ?? '');
+ $text .= $this->templateByName('#potwierdzenie-zapisu-do-newslettera');
+ $text .= ($settings['newsletter_footer'] ?? '');
+
+ $base = $ssl ? 'https' : 'http';
+ $link = '/newsletter/confirm/hash=' . $hash;
+ $text = str_replace('[LINK]', $link, $text);
+ $text = str_replace('[WYPISZ_SIE]', '', $text);
+
+ $text = preg_replace(
+ "-( ]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i",
+ "$1" . $base . "://" . $serverName . "$2$4",
+ $text
+ );
+ $text = preg_replace(
+ "-(]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i",
+ "$1" . $base . "://" . $serverName . "$2$4",
+ $text
+ );
+
+ $lang = \S::get_session('lang-' . \S::get_session('current-lang'));
+ $subject = $lang['potwierdz-zapisanie-sie-do-newslettera'] ?? 'Newsletter';
+ \S::send_email($email, $subject, $text);
+
+ $this->db->insert('pp_newsletter', ['email' => $email, 'hash' => $hash, 'status' => 0]);
+
+ return true;
+ }
+
+ public function sendQueued(int $limit, string $serverName, bool $ssl, string $unsubscribeLabel): bool
+ {
+ $settingsDetails = $this->settingsRepository->getSettings();
+
+ $results = $this->db->query('SELECT * FROM pp_newsletter_send ORDER BY id ASC LIMIT ' . (int)$limit);
+ $results = $results ? $results->fetchAll() : [];
+
+ if (!is_array($results) || empty($results)) {
+ return false;
+ }
+
+ $renderer = $this->getPreviewRenderer();
+ $articleRepo = $this->getArticleRepository();
+
+ foreach ($results as $row) {
+ $dates = explode(' - ', $row['dates']);
+
+ $articles = [];
+ if (isset($dates[0], $dates[1])) {
+ $articles = $articleRepo->articlesByDateAdd($dates[0], $dates[1]);
+ }
+
+ $text = $renderer->render(
+ is_array($articles) ? $articles : [],
+ $settingsDetails,
+ $this->templateDetails((int)$row['id_template']),
+ (string)$row['dates']
+ );
+
+ $base = $ssl ? 'https' : 'http';
+
+ $text = preg_replace(
+ "-( ]+src\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i",
+ "$1" . $base . "://" . $serverName . "$2$4",
+ $text
+ );
+ $text = preg_replace(
+ "-(]+href\s*=\s*['\"])(((?!'|\"|http://).)*)(['\"][^>]*>)-i",
+ "$1" . $base . "://" . $serverName . "$2$4",
+ $text
+ );
+
+ $hash = $this->getHashByEmail($row['email']);
+ $link = $base . "://" . $serverName . '/newsletter/unsubscribe/hash=' . $hash;
+ $text = str_replace('[WYPISZ_SIE]', '' . $unsubscribeLabel . '', $text);
+
+ \S::send_email($row['email'], 'Newsletter ze strony: ' . $serverName, $text);
+
+ $this->db->delete('pp_newsletter_send', ['id' => $row['id']]);
+ }
+
+ return true;
+ }
}
diff --git a/autoload/admin/App.php b/autoload/admin/App.php
index 3995c08..ba2b930 100644
--- a/autoload/admin/App.php
+++ b/autoload/admin/App.php
@@ -394,7 +394,8 @@ class App
global $mdb;
return new \admin\Controllers\ShopProductController(
new \Domain\Product\ProductRepository( $mdb ),
- new \Domain\Integrations\IntegrationsRepository( $mdb )
+ new \Domain\Integrations\IntegrationsRepository( $mdb ),
+ new \Domain\Languages\LanguagesRepository( $mdb )
);
},
'ShopClients' => function() {
diff --git a/autoload/admin/Controllers/SettingsController.php b/autoload/admin/Controllers/SettingsController.php
index 3fb7d36..16abd22 100644
--- a/autoload/admin/Controllers/SettingsController.php
+++ b/autoload/admin/Controllers/SettingsController.php
@@ -91,7 +91,7 @@ class SettingsController
$likeNormalized = '%' . $phraseNormalized . '%';
$items = [];
- $defaultLang = (string)\front\factory\Languages::default_language();
+ $defaultLang = (string)$this->languagesRepository->defaultLanguage();
try {
$productStmt = $mdb->query(
diff --git a/autoload/admin/Controllers/ShopCategoryController.php b/autoload/admin/Controllers/ShopCategoryController.php
index 61d70b5..38688fc 100644
--- a/autoload/admin/Controllers/ShopCategoryController.php
+++ b/autoload/admin/Controllers/ShopCategoryController.php
@@ -20,7 +20,7 @@ class ShopCategoryController
return \Tpl::view('shop-category/categories-list', [
'categories' => $this->repository->subcategories(null),
'level' => 0,
- 'dlang' => \front\factory\Languages::default_language(),
+ 'dlang' => $this->languagesRepository->defaultLanguage(),
]);
}
@@ -36,6 +36,7 @@ class ShopCategoryController
'pid' => \S::get('pid'),
'languages' => $this->languagesRepository->languagesList(),
'sort_types' => $this->repository->sortTypes(),
+ 'dlang' => $this->languagesRepository->defaultLanguage(),
]);
}
@@ -102,7 +103,7 @@ class ShopCategoryController
echo \Tpl::view('shop-category/category-browse-list', [
'categories' => $this->repository->subcategories(null),
'level' => 0,
- 'dlang' => \front\factory\Languages::default_language(),
+ 'dlang' => $this->languagesRepository->defaultLanguage(),
]);
exit;
}
diff --git a/autoload/admin/Controllers/ShopProductController.php b/autoload/admin/Controllers/ShopProductController.php
index 9a41bac..a18ad36 100644
--- a/autoload/admin/Controllers/ShopProductController.php
+++ b/autoload/admin/Controllers/ShopProductController.php
@@ -4,6 +4,7 @@ namespace admin\Controllers;
use Domain\Product\ProductRepository;
use Domain\Category\CategoryRepository;
use Domain\Integrations\IntegrationsRepository;
+use Domain\Languages\LanguagesRepository;
use admin\ViewModels\Forms\FormEditViewModel;
use admin\ViewModels\Forms\FormField;
use admin\ViewModels\Forms\FormTab;
@@ -19,11 +20,13 @@ class ShopProductController
{
private ProductRepository $repository;
private IntegrationsRepository $integrationsRepository;
+ private LanguagesRepository $languagesRepository;
- public function __construct(ProductRepository $repository, IntegrationsRepository $integrationsRepository)
+ public function __construct(ProductRepository $repository, IntegrationsRepository $integrationsRepository, LanguagesRepository $languagesRepository)
{
$this->repository = $repository;
$this->integrationsRepository = $integrationsRepository;
+ $this->languagesRepository = $languagesRepository;
}
// ─── Krok 6: Lista / widok ───────────────────────────────────────
@@ -35,7 +38,7 @@ class ShopProductController
{
$apiloEnabled = $this->integrationsRepository->getSetting( 'apilo', 'enabled' );
$shopproEnabled = $this->integrationsRepository->getSetting( 'shoppro', 'enabled' );
- $dlang = \front\factory\Languages::default_language();
+ $dlang = $this->languagesRepository->defaultLanguage();
$sortableColumns = [ 'id', 'name', 'price_brutto', 'status', 'promoted', 'quantity' ];
@@ -221,7 +224,7 @@ class ShopProductController
$sets = \shop\ProductSet::sets_list();
$producers = ( new \Domain\Producer\ProducerRepository( $db ) )->allProducers();
$units = ( new \Domain\Dictionaries\DictionariesRepository( $db ) )->allUnits();
- $dlang = \front\factory\Languages::default_language();
+ $dlang = $this->languagesRepository->defaultLanguage();
$viewModel = $this->buildProductFormViewModel(
$product, $languages, $categories, $layouts, $products, $sets, $producers, $units, $dlang
@@ -949,7 +952,7 @@ class ShopProductController
return \Tpl::view( 'shop-product/product-combination', [
'product' => $this->repository->findForAdmin( (int) \S::get( 'product_id' ) ),
'attributes' => ( new \Domain\Attribute\AttributeRepository( $db ) )->getAttributesListForCombinations(),
- 'default_language' => \front\factory\Languages::default_language(),
+ 'default_language' => $this->languagesRepository->defaultLanguage(),
'product_permutations' => $this->repository->getCombinationsForTable( (int) \S::get( 'product_id' ) ),
] );
}
@@ -1146,7 +1149,7 @@ class ShopProductController
return \Tpl::view( 'shop-product/mass-edit', [
'products' => $this->repository->allProductsForMassEdit(),
'categories' => $categoryRepository->subcategories( null ),
- 'dlang' => \front\factory\Languages::default_language(),
+ 'dlang' => $this->languagesRepository->defaultLanguage(),
] );
}
diff --git a/autoload/front/Controllers/NewsletterController.php b/autoload/front/Controllers/NewsletterController.php
new file mode 100644
index 0000000..befaf06
--- /dev/null
+++ b/autoload/front/Controllers/NewsletterController.php
@@ -0,0 +1,49 @@
+repository = $repository;
+ }
+
+ public function signin()
+ {
+ global $settings;
+
+ $result = [ 'status' => 'bad' ];
+
+ if ( $this->repository->signup( \S::get( 'email' ), $_SERVER['SERVER_NAME'], !empty( $settings['ssl'] ), $settings ) )
+ $result = [ 'status' => 'ok' ];
+
+ echo json_encode( $result );
+ exit;
+ }
+
+ public function confirm()
+ {
+ global $lang;
+
+ if ( $this->repository->confirmSubscription( \S::get( 'hash' ) ) )
+ \S::alert( $lang['email-zostal-dodany-do-listy-newsletter'] );
+
+ header( 'Location: /' );
+ exit;
+ }
+
+ public function unsubscribe()
+ {
+ global $lang;
+
+ if ( $this->repository->unsubscribe( \S::get( 'hash' ) ) )
+ \S::alert( $lang['email-zostal-usuniety-z-listy-newsletter'] );
+
+ header( 'Location: /' );
+ exit;
+ }
+}
diff --git a/autoload/front/Views/Languages.php b/autoload/front/Views/Languages.php
new file mode 100644
index 0000000..ec0e8ee
--- /dev/null
+++ b/autoload/front/Views/Languages.php
@@ -0,0 +1,12 @@
+ languages = $languages;
+ return $tpl -> render( 'site/languages' );
+ }
+}
diff --git a/autoload/front/view/class.Newsletter.php b/autoload/front/Views/Newsletter.php
similarity index 63%
rename from autoload/front/view/class.Newsletter.php
rename to autoload/front/Views/Newsletter.php
index e303a2e..be290d4 100644
--- a/autoload/front/view/class.Newsletter.php
+++ b/autoload/front/Views/Newsletter.php
@@ -1,9 +1,9 @@
render( 'newsletter/newsletter' );
diff --git a/autoload/front/controls/class.Newsletter.php b/autoload/front/controls/class.Newsletter.php
deleted file mode 100644
index 4ebb7f7..0000000
--- a/autoload/front/controls/class.Newsletter.php
+++ /dev/null
@@ -1,38 +0,0 @@
- 'bad' ];
-
- if ( \front\factory\Newsletter::newsletter_signin( \S::get( 'email' ) ) )
- $result = [ 'status' => 'ok' ];
-
- echo json_encode( $result );
- exit;
- }
-
- public static function confirm()
- {
- global $lang;
-
- if ( \front\factory\Newsletter::newsletter_confirm( \S::get( 'hash' ) ) )
- \S::alert( $lang['email-zostal-dodany-do-listy-newsletter'] );
-
- header( 'Location: /' );
- exit;
- }
-
- public static function unsubscribe()
- {
- global $lang;
-
- if ( \front\factory\Newsletter::newsletter_unsubscribe( \S::get( 'hash' ) ) )
- \S::alert( $lang['email-zostal-usuniety-z-listy-newsletter'] );
-
- header( 'Location: /' );
- exit;
- }
-}
\ No newline at end of file
diff --git a/autoload/front/controls/class.Site.php b/autoload/front/controls/class.Site.php
index 6a7ff08..d91337d 100644
--- a/autoload/front/controls/class.Site.php
+++ b/autoload/front/controls/class.Site.php
@@ -53,14 +53,21 @@ class Site
if ( $category )
return \front\view\ShopCategory::category_view( $category, $lang_id, \S::get( 'bs' ) );
- // stare klasy
- $class = '\front\controls\\';
-
- $results = explode( '_', \S::get( 'module' ) );
- if ( is_array( $results ) ) foreach ( $results as $row )
- $class .= ucfirst( $row );
-
+ // nowe kontrolery z DI
+ $module = \S::get( 'module' );
$action = \S::get( 'action' );
+ $controllerFactories = self::getControllerFactories();
+
+ $moduleName = implode( '', array_map( 'ucfirst', explode( '_', $module ) ) );
+ if ( isset( $controllerFactories[$moduleName] ) and $action )
+ {
+ $controller = $controllerFactories[$moduleName]();
+ if ( method_exists( $controller, $action ) )
+ return $controller->$action();
+ }
+
+ // stare klasy
+ $class = '\front\controls\\' . $moduleName;
if ( class_exists( $class ) and method_exists( new $class, $action ) )
return call_user_func_array( array( $class, $action ), array() );
@@ -132,5 +139,17 @@ class Site
if ( file_exists( 'modules/actions.php' ) )
include 'modules/actions.php';
}
+
+ public static function getControllerFactories()
+ {
+ return [
+ 'Newsletter' => function() {
+ global $mdb;
+ return new \front\Controllers\NewsletterController(
+ new \Domain\Newsletter\NewsletterRepository( $mdb )
+ );
+ },
+ ];
+ }
}
?>
diff --git a/autoload/front/factory/class.Languages.php b/autoload/front/factory/class.Languages.php
deleted file mode 100644
index 543755b..0000000
--- a/autoload/front/factory/class.Languages.php
+++ /dev/null
@@ -1,29 +0,0 @@
-defaultLanguage();
- }
-
- public static function active_languages()
- {
- global $mdb;
- $repo = new \Domain\Languages\LanguagesRepository($mdb);
- return $repo->activeLanguages();
- }
-
- public static function lang_translations($language = 'pl')
- {
- global $mdb;
- $repo = new \Domain\Languages\LanguagesRepository($mdb);
- return $repo->translations($language);
- }
-}
diff --git a/autoload/front/factory/class.Newsletter.php b/autoload/front/factory/class.Newsletter.php
deleted file mode 100644
index e1d436a..0000000
--- a/autoload/front/factory/class.Newsletter.php
+++ /dev/null
@@ -1,133 +0,0 @@
- get( 'pp_newsletter', 'id', [ 'hash' => $hash ] ) )
- return false;
- else
- $mdb -> delete( 'pp_newsletter', [ 'status' => 1 ], [ 'id' => $id ] );
- return true;
- }
-
- 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;
- }
-
- public static function newsletter_send( $limit = 5 )
- {
- global $mdb, $settings, $lang;
- $settingsRepository = new \Domain\Settings\SettingsRepository( $mdb );
- $newsletterRepository = new \Domain\Newsletter\NewsletterRepository( $mdb, $settingsRepository );
- $previewRenderer = new \Domain\Newsletter\NewsletterPreviewRenderer();
- $settingsDetails = $settingsRepository -> getSettings();
-
- $results = $mdb -> query( 'SELECT * FROM pp_newsletter_send ORDER BY id ASC LIMIT ' . $limit ) -> fetchAll();
- if ( is_array( $results ) and !empty( $results ) )
- {
- foreach ( $results as $row )
- {
- $dates = explode( ' - ', $row['dates'] );
-
- $articles = [];
- $articleRepository = new \Domain\Article\ArticleRepository( $mdb );
- if ( isset( $dates[0], $dates[1] ) )
- $articles = $articleRepository->articlesByDateAdd( $dates[0], $dates[1] );
-
- $text = $previewRenderer -> render(
- is_array( $articles ) ? $articles : [],
- $settingsDetails,
- $newsletterRepository -> templateDetails( (int)$row['id_template'] ),
- (string)$row['dates']
- );
-
- if ( $settings['ssl'] ) $base = 'https'; else $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 = $base . "://" . $_SERVER['SERVER_NAME'] . '/newsletter/unsubscribe/hash=' . \front\factory\Newsletter::get_hash( $row['email'] );
-
- $text = str_replace( '[WYPISZ_SIE]', '' . $lang['wypisz-sie'] . '', $text );
-
- \S::send_email( $row['email'], 'Newsletter ze strony: ' . $_SERVER['SERVER_NAME'], $text );
-
- $mdb -> delete( 'pp_newsletter_send', [ 'id' => $row['id'] ] );
- }
- return true;
- }
- return false;
- }
-
- public static function get_hash( $email )
- {
- global $mdb;
- return $mdb -> get( 'pp_newsletter', 'hash', [ 'email' => $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'];
-
- $settings['ssl'] ? $base = 'https' : $base = 'http';
-
- $link = '/newsletter/confirm/hash=' . $hash;
-
- $text = str_replace( '[LINK]', $link, $text );
-
- $text = str_replace( '[WYPISZ_SIE]', '', $text );
-
- $regex = "-( ]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
- $text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
-
- $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
- $text = preg_replace( $regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $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;
- }
-
- public static function get_template( $template_name )
- {
- global $mdb;
- $repository = new \Domain\Newsletter\NewsletterRepository( $mdb );
- return $repository -> templateByName( (string)$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;
- }
-}
diff --git a/autoload/front/factory/class.Pages.php b/autoload/front/factory/class.Pages.php
index 239b012..920d0fe 100644
--- a/autoload/front/factory/class.Pages.php
+++ b/autoload/front/factory/class.Pages.php
@@ -32,7 +32,7 @@ class Pages
$page['language']['seo_link'] ? $url = '/' . $page['language']['seo_link'] : $url = '/s-' . $page['id'] . '-' . \S::seo( $page['language']['title'] );
- if ( $lang_id != \front\factory\Languages::default_language() and $url != '#' )
+ if ( $lang_id != ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() and $url != '#' )
$url = '/' . $lang_id . $url;
return $url;
diff --git a/autoload/front/factory/class.ShopCategory.php b/autoload/front/factory/class.ShopCategory.php
index 3eb8d20..0cf6fd4 100644
--- a/autoload/front/factory/class.ShopCategory.php
+++ b/autoload/front/factory/class.ShopCategory.php
@@ -35,7 +35,7 @@ class ShopCategory
$category['language']['seo_link'] ? $url = '/' . $category['language']['seo_link'] : $url = '/k-' . $category['id'] . '-' . \S::seo( $category['language']['title'] );
- if ( \S::get_session( 'current-lang' ) != \front\factory\Languages::default_language() and $url != '#' )
+ if ( \S::get_session( 'current-lang' ) != ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() and $url != '#' )
$url = '/' . \S::get_session( 'current-lang' ) . $url;
return $url;
diff --git a/autoload/front/factory/class.ShopClient.php b/autoload/front/factory/class.ShopClient.php
index 5a1e450..81d3d7e 100644
--- a/autoload/front/factory/class.ShopClient.php
+++ b/autoload/front/factory/class.ShopClient.php
@@ -93,7 +93,7 @@ class ShopClient
if ( $data = $mdb -> get( 'pp_shop_clients', [ 'id', 'email', 'register_date' ], [ 'AND' => [ 'hash' => $hash, 'status' => 1, 'password_recovery' => 1 ] ] ) )
{
$text = $settings['newsletter_header'];
- $text .= \front\factory\Newsletter::get_template( '#nowe-haslo' );
+ $text .= ( new \Domain\Newsletter\NewsletterRepository( $mdb ) )->templateByName( '#nowe-haslo' );
$text .= $settings['newsletter_footer'];
$settings['ssl'] ? $base = 'https' : $base = 'http';
@@ -129,7 +129,7 @@ class ShopClient
if ( $hash = $mdb -> get( 'pp_shop_clients', 'hash', [ 'AND' => [ 'email' => $email, 'status' => 1 ] ] ) )
{
$text = $settings['newsletter_header'];
- $text .= \front\factory\Newsletter::get_template( '#odzyskiwanie-hasla-link' );
+ $text .= ( new \Domain\Newsletter\NewsletterRepository( $mdb ) )->templateByName( '#odzyskiwanie-hasla-link' );
$text .= $settings['newsletter_footer'];
$settings['ssl'] ? $base = 'https' : $base = 'http';
@@ -164,7 +164,7 @@ class ShopClient
$email = $mdb -> get( 'pp_shop_clients', 'email', [ 'id' => $id ] );
$text = $settings['newsletter_header'];
- $text .= \front\factory\Newsletter::get_template( '#potwierdzenie-aktywacji-konta' );
+ $text .= ( new \Domain\Newsletter\NewsletterRepository( $mdb ) )->templateByName( '#potwierdzenie-aktywacji-konta' );
$text .= $settings['newsletter_footer'];
$settings['ssl'] ? $base = 'https' : $base = 'http';
@@ -201,7 +201,7 @@ class ShopClient
] ) )
{
$text = $settings['newsletter_header'];
- $text .= \front\factory\Newsletter::get_template( '#potwierdzenie-rejestracji' );
+ $text .= ( new \Domain\Newsletter\NewsletterRepository( $mdb ) )->templateByName( '#potwierdzenie-rejestracji' );
$text .= $settings['newsletter_footer'];
$settings['ssl'] ? $base = 'https' : $base = 'http';
diff --git a/autoload/front/view/class.Languages.php b/autoload/front/view/class.Languages.php
deleted file mode 100644
index f4677ae..0000000
--- a/autoload/front/view/class.Languages.php
+++ /dev/null
@@ -1,12 +0,0 @@
- languages = \front\factory\Languages::active_languages();
- return $tpl -> render( 'site/languages' );
- }
-}
diff --git a/autoload/front/view/class.Site.php b/autoload/front/view/class.Site.php
index 7d9d677..e2e51b0 100644
--- a/autoload/front/view/class.Site.php
+++ b/autoload/front/view/class.Site.php
@@ -106,7 +106,7 @@ class Site
] ),
$html );
$html = str_replace( '[NEWSLETTER]',
- \front\view\Newsletter::newsletter(),
+ \front\Views\Newsletter::render(),
$html );
$html = str_replace( '[UZYTKOWNIK_MINI_LOGOWANIE]',
\front\view\ShopClient::mini_login(),
@@ -333,7 +333,7 @@ class Site
$html = str_replace( '[TITLE]', $page['language']['meta_title'] ? $page['language']['meta_title'] . ' | ' . $settings['firm_name'] : $page['language']['title'] . ' | ' . $settings['firm_name'], $html );
$html = str_replace( '[META_KEYWORDS]', $page['language']['meta_keywords'], $html );
$html = str_replace( '[META_DESCRIPTION]', $page['language']['meta_description'], $html );
- $html = str_replace( '[JEZYKI]', \front\view\Languages::languages(), $html );
+ $html = str_replace( '[JEZYKI]', \front\Views\Languages::render( ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->activeLanguages() ), $html );
$html = str_replace( '[TYTUL_STRONY]', self::title( $page['language']['title'], $page['show_title'], $page['language']['page_title'] ), $html );
$html = str_replace( '[WYSZUKIWARKA]', \shop\Search::simple_form(), $html );
diff --git a/autoload/shop/class.Category.php b/autoload/shop/class.Category.php
index bf579b7..4158a9e 100644
--- a/autoload/shop/class.Category.php
+++ b/autoload/shop/class.Category.php
@@ -19,7 +19,7 @@ class Category implements \ArrayAccess
global $mdb;
if ( !$lang_id )
- $lang_id = \front\factory\Languages::default_language();
+ $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage();
return $mdb -> get( 'pp_shop_categories_langs', 'title', [ 'AND' => [ 'category_id' => $category_id, 'lang_id' => $lang_id ] ] );
}
diff --git a/autoload/shop/class.Product.php b/autoload/shop/class.Product.php
index 770e55e..1f27100 100644
--- a/autoload/shop/class.Product.php
+++ b/autoload/shop/class.Product.php
@@ -9,7 +9,7 @@ class Product implements \ArrayAccess
global $mdb;
if ( !$lang_id )
- $lang_id = \front\factory\Languages::default_language();
+ $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage();
$result = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $product_id ] );
if ( \S::is_array_fix( $result ) ) foreach ( $result as $key => $val )
@@ -292,7 +292,7 @@ class Product implements \ArrayAccess
global $mdb;
if ( !$lang_id )
- $lang_id = \front\factory\Languages::default_language();
+ $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage();
$repository = new \Domain\Product\ProductRepository($mdb);
return $repository->getName($product_id, $lang_id);
@@ -377,7 +377,7 @@ class Product implements \ArrayAccess
{
global $mdb;
- $lang_id = \front\factory\Languages::default_language();
+ $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage();
$results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] );
if ( \S::is_array_fix( $results ) ) foreach ( $results as $row )
diff --git a/autoload/shop/class.ProductAttribute.php b/autoload/shop/class.ProductAttribute.php
index 805a0d5..d000fea 100644
--- a/autoload/shop/class.ProductAttribute.php
+++ b/autoload/shop/class.ProductAttribute.php
@@ -47,7 +47,7 @@ class ProductAttribute implements \ArrayAccess
global $mdb;
if ( !$lang_id )
- $lang_id = \front\factory\Languages::default_language();
+ $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage();
$result = $mdb -> get( 'pp_shop_attributes', '*', [ 'id' => $attribute_id ] );
if ( \S::is_array_fix( $result ) ) foreach ( $result as $key => $val )
diff --git a/cron/cron-xml.php b/cron/cron-xml.php
index 2b123e9..e43a549 100644
--- a/cron/cron-xml.php
+++ b/cron/cron-xml.php
@@ -51,6 +51,6 @@ $mdb = new medoo( [
] );
$settings = \front\factory\Settings::settings_details();
-$lang_id = \front\factory\Languages::default_language();
+$lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage();
( new \Domain\Product\ProductRepository( $mdb ) )->generateGoogleFeedXml();
\ No newline at end of file
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 9d1ab8a..a6a9c41 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -4,6 +4,30 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
+## ver. 0.279 (2026-02-16) - Newsletter + Languages frontend migration, front\Controllers, front\Views
+
+- **Languages (view)** — migracja do nowego namespace
+ - USUNIĘTA: `front\factory\Languages` — fasada niepotrzebna, wszystkie zależności przepięte na `Domain\Languages\LanguagesRepository`
+ - USUNIĘTA: `front\view\Languages` → przeniesiona do `front\Views\Languages` (nowy namespace, bez `class.` prefix)
+ - UPDATE: 26 plików przepiętych z fasady na repozytorium (kontrolery DI, entry points, szablony, shop classes)
+ - UPDATE: `admin\App` — DI factory dla `ShopProductController` rozszerzona o `LanguagesRepository`
+
+- **Newsletter (frontend)** — pełna migracja na Domain
+ - NOWE METODY w `NewsletterRepository`: `unsubscribe()`, `confirmSubscription()`, `getHashByEmail()`, `removeByEmail()`, `signup()`, `sendQueued()`
+ - NOWY: `front\Controllers\NewsletterController` — pierwszy frontowy kontroler z DI (nowy namespace `front\Controllers\`)
+ - NOWY: `front\Views\Newsletter` — czysty VIEW (nowy namespace `front\Views\`)
+ - USUNIĘTA: `front\factory\Newsletter` — logika przeniesiona do `NewsletterRepository`
+ - USUNIĘTA: `front\view\Newsletter` → zastąpiona przez `front\Views\Newsletter`
+ - USUNIĘTA: `front\controls\Newsletter` → zastąpiona przez `front\Controllers\NewsletterController`
+ - UPDATE: `front\controls\Site::route()` — nowy routing: `getControllerFactories()` (DI) → fallback stare `front\controls\`
+ - UPDATE: `front\factory\ShopClient` — 4x `get_template()` przepięte na `NewsletterRepository::templateByName()`
+ - UPDATE: `index.php` — `newsletter_send()` przepięte na `$repo->sendQueued()`
+ - FIX: `newsletter_unsubscribe()` — błędna składnia medoo `delete()` (3 argumenty zamiast 2)
+ - UPDATE: `tests/bootstrap.php` — dodane stuby: `S::email_check()`, `S::get_session()`, `S::set_session()`
+ - Testy: 437 OK, 1398 asercji (+10 nowych testów w NewsletterRepositoryTest)
+
+---
+
## ver. 0.278 (2026-02-16) - Settings + Languages frontend migration
- **Settings + Languages (frontend)** — pierwszy etap refaktoringu frontendu
diff --git a/docs/DATABASE_STRUCTURE.md b/docs/DATABASE_STRUCTURE.md
index 1d14bd2..2547590 100644
--- a/docs/DATABASE_STRUCTURE.md
+++ b/docs/DATABASE_STRUCTURE.md
@@ -326,7 +326,7 @@ Adresy e-mail zapisane do newslettera.
| hash | Hash potwierdzenia/wypisu |
| status | 1 = potwierdzony, 0 = oczekujacy |
-**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter`
+**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\Controllers\\NewsletterController`
## pp_newsletter_send
Kolejka wysylki newslettera.
@@ -338,7 +338,7 @@ Kolejka wysylki newslettera.
| dates | Zakres dat artykulow (tekst) |
| id_template | FK do `pp_newsletter_templates` (NULL gdy brak szablonu) |
-**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter::newsletter_send()`
+**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`
## pp_newsletter_templates
Szablony tresci e-maili (uzytkownik + administracyjne/systemowe).
@@ -350,10 +350,12 @@ Szablony tresci e-maili (uzytkownik + administracyjne/systemowe).
| text | Tresc HTML szablonu |
| is_admin | 1 = szablon administracyjny/systemowy, 0 = szablon uzytkownika |
-**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `admin\\Controllers\\NewsletterController`, `front\\factory\\Newsletter`
+**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `admin\\Controllers\\NewsletterController`
**Aktualizacja 2026-02-12 (ver. 0.257):** modul `/admin/newsletter` korzysta z `Domain\\Newsletter\\NewsletterRepository` (DI kontroler + fasada legacy).
+**Aktualizacja 2026-02-16 (ver. 0.279):** `front\\factory\\Newsletter` usunięta — logika przeniesiona do `NewsletterRepository`. Frontend korzysta z `front\\Controllers\\NewsletterController` (DI).
+
## pp_scontainers
Kontenery statyczne (modul /admin/scontainers).
diff --git a/docs/FRONTEND_REFACTORING_PLAN.md b/docs/FRONTEND_REFACTORING_PLAN.md
index 4f2edf3..11cbd03 100644
--- a/docs/FRONTEND_REFACTORING_PLAN.md
+++ b/docs/FRONTEND_REFACTORING_PLAN.md
@@ -36,9 +36,9 @@ Panel administratora (33 moduły) został w pełni zmigrowany na architekturę D
| ShopPaymentMethod | ZMIGROWANA (Domain) | — |
| ShopStatuses | ZMIGROWANA (Domain) | — |
| Scontainers | ZMIGROWANA (Domain) | — |
-| Newsletter | CZĘŚCIOWO zmigrowana | ŚREDNI |
+| Newsletter | ZMIGROWANA (Domain) — usunięta | — |
| Settings | Fasada (BUG: get_single_settings_value ignoruje $param) | NISKI |
-| Languages | Fasada | NISKI |
+| Languages | USUNIĘTA — przepięta na Domain | — |
| Layouts | Fasada | NISKI |
| Banners | Fasada | NISKI |
| Menu | Fasada | NISKI |
@@ -51,7 +51,8 @@ Panel administratora (33 moduły) został w pełni zmigrowany na architekturę D
|-------|--------|
| Site | KRYTYCZNY — show() ~600 linii, pattern substitution engine |
| ShopCategory | VIEW z logiką routingu (infinite scroll vs pagination) |
-| Articles, Banners, Languages, Menu, Newsletter, Scontainers | Czyste VIEW |
+| Articles, Banners, Menu, Scontainers | Czyste VIEW |
+| Languages, Newsletter | PRZENIESIONE do `front\Views\` (nowy namespace) |
| ShopClient, ShopOrder, ShopPaymentMethod | Czyste VIEW |
| ShopTransport | PUSTA klasa (placeholder) |
@@ -109,7 +110,7 @@ articles(8), banner(2), controls(1), menu(4), newsletter(2), scontainers(1), sho
1. **KRYTYCZNY** `front\factory\ShopClient::login()` — hardcoded password bypass `'Legia1916'`
2. `front\factory\Settings::get_single_settings_value()` — ignoruje `$param`, zawsze zwraca `firm_name`
-3. `front\factory\Newsletter::newsletter_unsubscribe()` — błędna składnia SQL w delete
+3. ~~`front\factory\Newsletter::newsletter_unsubscribe()` — błędna składnia SQL w delete~~ **NAPRAWIONE** — `NewsletterRepository::unsubscribe()` z poprawną składnią medoo `delete()`
4. `cms\Layout::__get()` — referuje nieistniejące `$this->data`
5. `shop\Search` — typo w use: `shop\Produt` (brak 'c')
@@ -173,6 +174,33 @@ Legacy Cleanup
---
+### Etap: Newsletter Frontend — ZREALIZOWANY
+
+**Cel:** Przeniesienie logiki frontendowej z `front\factory\Newsletter` do `Domain\Newsletter\NewsletterRepository`. Migracja view do nowego namespace `front\Views`.
+
+**DODANE METODY (do istniejącej klasy `NewsletterRepository`):**
+- `unsubscribe(string $hash): bool` — FIX: poprawna składnia medoo `delete()` (2 args zamiast 3)
+- `confirmSubscription(string $hash): bool`
+- `getHashByEmail(string $email): ?string`
+- `removeByEmail(string $email): bool`
+- `signup(string $email, string $serverName, bool $ssl, array $settings): bool`
+- `sendQueued(int $limit, string $serverName, bool $ssl, string $unsubscribeLabel): bool`
+- Konstruktor rozszerzony o opcjonalne: `ArticleRepository`, `NewsletterPreviewRenderer` (lazy-init)
+- Testy: 10 nowych testów w `NewsletterRepositoryTest`
+
+**ZMIANA:**
+- `front/factory/Newsletter` → USUNIĘTA (logika przeniesiona do `NewsletterRepository`)
+- `front/view/Newsletter` → USUNIĘTA, zastąpiona przez `front/Views/Newsletter` (nowy namespace, bez `class.` prefix)
+- `front/controls/Newsletter` → thin wrapper na `NewsletterRepository`
+- `front/view/Site::show()` → `\front\Views\Newsletter::render()`
+- `index.php` → `$newsletterRepo->sendQueued()`
+- `front/factory/ShopClient` (4 miejsca) → `NewsletterRepository::templateByName()`
+- `tests/bootstrap.php` — dodane stuby: `S::email_check()`, `S::get_session()`, `S::set_session()`
+
+**BUG FIX:** `newsletter_unsubscribe()` — medoo `delete()` wywoływane z 3 argumentami zamiast 2
+
+---
+
### Etap: Category Frontend Service
**Cel:** Migracja `front\factory\ShopCategory` do Domain.
@@ -435,7 +463,7 @@ front\factory\ShopPromotion::promotion_type_XX() → shop\Product::is_product_on
- PHPDoc do wszystkich nowych klas Domain z `@since`
- Aktualizacja `tests/bootstrap.php`
- BUG FIX: `shop\Search` — typo `shop\Produt` → `shop\Product`
-- BUG FIX: `front\factory\Newsletter::newsletter_unsubscribe()` — poprawka SQL
+- ~~BUG FIX: `front\factory\Newsletter::newsletter_unsubscribe()` — poprawka SQL~~ **ZREALIZOWANE** w etapie Newsletter Frontend
---
@@ -471,6 +499,16 @@ front\factory\ShopPromotion::promotion_type_XX() → shop\Product::is_product_on
- Namespace `\front\Controllers\` → `autoload/front/Controllers/`
- **Klasy Domain sa wspolne dla admin i frontendu** — NIE tworzymy osobnych FrontendService/AdminService. Metody frontendowe (z cache Redis) dodajemy do istniejacych repozytoriow/serwisow Domain. Klasy sa ladowane lazy (instancja tworzona dopiero przy wywolaniu), wiec nie wplywaja na wydajnosc.
+### Nazewnictwo plikow
+- Nowe klasy: `NazwaKlasy.php` (bez przedrostka `class.`)
+- Legacy: `class.NazwaKlasy.php` — zostawiamy do momentu migracji danej klasy
+- Autoloader obsluguje oba formaty (probuje `class.X.php`, potem `X.php`)
+- Nowe katalogi z duzej litery: `Views/`, `Controllers/` (legacy: `view/`, `controls/`, `factory/`)
+
+### Statyczne vs instancyjne metody
+- **Statyczne** — gdy klasa jest bezstanowa (brak konstruktora, brak properties, brak DI). Czyste funkcje: dane wchodzą, wynik wychodzi. Przykład: klasy VIEW (`front\Views\Languages::render($data)`, `front\view\Banners::banners($data)`)
+- **Instancyjne** — gdy klasa ma zależności do wstrzyknięcia (repozytoria, serwisy) lub trzyma stan. Przykład: kontrolery z DI (`ShopProductController` z `ProductRepository`, `LanguagesRepository`)
+
### Weryfikacja po każdym etapie
1. `composer test` (pełny suite PHPUnit)
2. Manualne sprawdzenie frontendu: strona główna, kategoria, produkt, koszyk, zamówienie
diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md
index 3b3860f..b618153 100644
--- a/docs/PROJECT_STRUCTURE.md
+++ b/docs/PROJECT_STRUCTURE.md
@@ -203,7 +203,8 @@ autoload/
│ ├── Layouts/
│ │ └── LayoutsRepository.php
│ ├── Newsletter/
-│ │ └── NewsletterRepository.php
+│ │ ├── NewsletterRepository.php
+│ │ └── NewsletterPreviewRenderer.php
│ ├── Scontainers/
│ │ └── ScontainersRepository.php
│ ├── Dictionaries/
@@ -231,8 +232,13 @@ autoload/
│ ├── controls/ # Stare kontrolery (niezależny fallback)
│ ├── factory/ # Stare helpery (niezależny fallback)
│ └── view/ # Widoki (statyczne - bez zmian)
+├── front/
+│ ├── Controllers/ # Nowe kontrolery frontendowe (namespace \front\Controllers\) z DI
+│ ├── Views/ # Nowe widoki (namespace \front\Views\) — czyste VIEW, statyczne
+│ ├── controls/ # Legacy kontrolery (fallback)
+│ ├── factory/ # Legacy helpery (stopniowo migrowane)
+│ └── view/ # Legacy widoki
├── shop/ # Legacy - fasady do Domain
-└── front/factory/ # Legacy - stopniowo migrowane
```
**Aktualizacja 2026-02-14 (ver. 0.268):**
@@ -373,5 +379,15 @@ Pelna dokumentacja testow: `TESTING.md`
- Usunieto stary plik `autoload/admin/class.Site.php`.
- Pelna migracja admin zakonczona — wszystkie moduly na Domain + DI + Controllers.
+## Aktualizacja 2026-02-16 (ver. 0.279) - Newsletter + Languages frontend migration
+- Usunięta fasada `front\factory\Languages` — wszystkie 26 zależności przepięte bezpośrednio na `Domain\Languages\LanguagesRepository`.
+- Usunięta fasada `front\factory\Newsletter` — logika przeniesiona do `Domain\Newsletter\NewsletterRepository` (6 nowych metod frontendowych).
+- Usunięty stary kontroler `front\controls\Newsletter` i widok `front\view\Newsletter`.
+- Utworzony nowy namespace `front\Controllers\` — pierwszy frontowy kontroler z DI: `NewsletterController`.
+- Utworzony nowy namespace `front\Views\` — czyste widoki statyczne: `Languages`, `Newsletter`.
+- Zaktualizowany routing w `front\controls\Site::route()` — `getControllerFactories()` (DI) z fallbackiem na stare `front\controls\`.
+- Przepięte 4 wywołania `Newsletter::get_template()` w `front\factory\ShopClient` na `NewsletterRepository::templateByName()`.
+- FIX: `newsletter_unsubscribe()` — błędna składnia medoo `delete()`.
+
---
*Dokument aktualizowany: 2026-02-16*
diff --git a/docs/TESTING.md b/docs/TESTING.md
index a44d000..a167208 100644
--- a/docs/TESTING.md
+++ b/docs/TESTING.md
@@ -36,7 +36,14 @@ Alternatywnie (Git Bash):
Ostatnio zweryfikowano: 2026-02-16
```text
-OK (427 tests, 1378 assertions)
+OK (437 tests, 1398 assertions)
+```
+
+Aktualizacja po migracji Newsletter + Languages frontend (2026-02-16, ver. 0.279):
+```text
+Pelny suite: OK (437 tests, 1398 assertions)
+Nowe testy: NewsletterRepositoryTest (+10: unsubscribe, confirmSubscription, getHashByEmail, removeByEmail, signup, constructorOptionalDeps)
+Zaktualizowane: tests/bootstrap.php (stuby: S::email_check, S::get_session, S::set_session)
```
Aktualizacja po migracji Settings + Languages frontend (2026-02-16, ver. 0.278):
diff --git a/docs/UPDATE_INSTRUCTIONS.md b/docs/UPDATE_INSTRUCTIONS.md
index 3f86ee0..34c1e1d 100644
--- a/docs/UPDATE_INSTRUCTIONS.md
+++ b/docs/UPDATE_INSTRUCTIONS.md
@@ -18,16 +18,17 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
## Procedura tworzenia nowej aktualizacji
-## Status biezacej aktualizacji (ver. 0.278)
+## Status biezacej aktualizacji (ver. 0.279)
-- Wersja udostepniona: `0.278` (data: 2026-02-16).
+- Wersja udostepniona: `0.279` (data: 2026-02-16).
- Pliki publikacyjne:
- - `updates/0.20/ver_0.278.zip`
+ - `updates/0.20/ver_0.279.zip`
+ - `updates/0.20/ver_0.279_files.txt`
- Pliki metadanych aktualizacji:
- - `updates/changelog.php` (dodany wpis `ver. 0.278`)
- - `updates/versions.php` (`$current_ver = 278`)
+ - `updates/changelog.php` (dodany wpis `ver. 0.279`)
+ - `updates/versions.php` (`$current_ver = 279`)
- Weryfikacja testow przed publikacja:
- - `OK (427 tests, 1378 assertions)`
+ - `OK (437 tests, 1398 assertions)`
### 1. Określ numer wersji
Sprawdź ostatnią wersję w `updates/` i zwiększ o 1.
diff --git a/index.php b/index.php
index 22ef03d..5a1353e 100644
--- a/index.php
+++ b/index.php
@@ -61,15 +61,17 @@ $mdb = new medoo( [
\front\controls\Site::check_url_params();
+$langRepo = new \Domain\Languages\LanguagesRepository( $mdb );
+
if ( !$lang_id = \S::get_session( 'current-lang' ) )
{
- $lang_id = \front\factory\Languages::default_language();
+ $lang_id = $langRepo->defaultLanguage();
\S::set_session( 'current-lang', $lang_id );
}
if ( !$lang = \S::get_session( 'lang-' . $lang_id ) )
{
- $lang = \front\factory\Languages::lang_translations( $lang_id );
+ $lang = $langRepo->translations( $lang_id );
\S::set_session( 'lang-' . $lang_id, $lang );
}
@@ -159,7 +161,8 @@ if ( $settings[ 'statistic_code' ] )
$out = strrev( implode( strrev( $settings[ 'statistic_code' ] . ' |