fabryka kontrolera (DI) */ private static $newControllers = []; public static function finalize_admin_login( array $user, string $domain, string $cookie_name, bool $remember = false ) { \Shared\Helpers\Helpers::set_session( 'user', $user ); \Shared\Helpers\Helpers::delete_session( 'twofa_pending' ); if ( $remember ) { $payloadArr = [ 'login' => $user['login'], 'ts' => time() ]; $json = json_encode( $payloadArr, JSON_UNESCAPED_SLASHES ); $sig = hash_hmac( 'sha256', $json, self::APP_SECRET_KEY ); $payload = base64_encode( $json . '.' . $sig ); setcookie( $cookie_name, $payload, [ 'expires' => time() + ( 86400 * 14 ), 'path' => '/', 'domain' => $domain, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax', ] ); } } public static function special_actions() { global $mdb; $sa = \Shared\Helpers\Helpers::get( 's-action' ); if ( !$sa ) return; if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) { $csrfToken = isset( $_POST['_csrf_token'] ) ? (string) $_POST['_csrf_token'] : ''; if ( !\Shared\Security\CsrfToken::validate( $csrfToken ) ) { \Shared\Helpers\Helpers::alert( 'Nieprawidłowy token bezpieczeństwa. Spróbuj ponownie.' ); header( 'Location: /admin/' ); exit; } } $domain = preg_replace( '/^www\./', '', $_SERVER['SERVER_NAME'] ); $cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain ); $users = new \Domain\User\UserRepository( $mdb ); switch ( $sa ) { case 'user-logon': $login = \Shared\Helpers\Helpers::get( 'login' ); $pass = \Shared\Helpers\Helpers::get( 'password' ); $result = $users->logon( $login, $pass ); if ( $result == 1 ) { $user = $users->details( $login ); if ( !$user ) { \Shared\Helpers\Helpers::alert( 'Błąd logowania.' ); header( 'Location: /admin/' ); exit; } if ( $user['twofa_enabled'] == 1 ) { \Shared\Helpers\Helpers::set_session( 'twofa_pending', [ 'uid' => (int) $user['id'], 'login' => $login, 'remember' => (bool) \Shared\Helpers\Helpers::get( 'remember' ), 'started' => time(), ] ); if ( !$users->sendTwofaCode( (int) $user['id'] ) ) { \Shared\Helpers\Helpers::alert( 'Nie udało się wysłać kodu 2FA. Spróbuj ponownie.' ); \Shared\Helpers\Helpers::delete_session( 'twofa_pending' ); header( 'Location: /admin/' ); exit; } header( 'Location: /admin/user/twofa/' ); exit; } \Shared\Security\CsrfToken::regenerate(); self::finalize_admin_login( $user, $domain, $cookie_name, (bool) \Shared\Helpers\Helpers::get( 'remember' ) ); header( 'Location: /admin/articles/list/' ); exit; } if ( $result == -1 ) \Shared\Helpers\Helpers::alert( 'Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.' ); else \Shared\Helpers\Helpers::alert( 'Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.' ); header( 'Location: /admin/' ); exit; case 'user-2fa-verify': $pending = \Shared\Helpers\Helpers::get_session( 'twofa_pending' ); if ( !$pending || empty( $pending['uid'] ) ) { \Shared\Helpers\Helpers::alert( 'Sesja 2FA wygasła. Zaloguj się ponownie.' ); header( 'Location: /admin/' ); exit; } $code = trim( (string) \Shared\Helpers\Helpers::get( 'twofa' ) ); if ( !preg_match( '/^\d{6}$/', $code ) ) { \Shared\Helpers\Helpers::alert( 'Nieprawidłowy format kodu.' ); header( 'Location: /admin/user/twofa/' ); exit; } if ( !$users->verifyTwofaCode( (int) $pending['uid'], $code ) ) { \Shared\Helpers\Helpers::alert( 'Błędny lub wygasły kod.' ); header( 'Location: /admin/user/twofa/' ); exit; } $user = $users->details( $pending['login'] ); if ( !$user ) { \Shared\Helpers\Helpers::delete_session( 'twofa_pending' ); \Shared\Helpers\Helpers::alert( 'Sesja wygasła. Zaloguj się ponownie.' ); header( 'Location: /admin/' ); exit; } \Shared\Security\CsrfToken::regenerate(); self::finalize_admin_login( $user, $domain, $cookie_name, !empty( $pending['remember'] ) ); header( 'Location: /admin/articles/list/' ); exit; case 'user-2fa-resend': $pending = \Shared\Helpers\Helpers::get_session( 'twofa_pending' ); if ( !$pending || empty( $pending['uid'] ) ) { \Shared\Helpers\Helpers::alert( 'Sesja 2FA wygasła. Zaloguj się ponownie.' ); header( 'Location: /admin/' ); exit; } if ( !$users->sendTwofaCode( (int) $pending['uid'], true ) ) \Shared\Helpers\Helpers::alert( 'Kod można wysłać ponownie po krótkiej przerwie.' ); else \Shared\Helpers\Helpers::alert( 'Nowy kod został wysłany.' ); header( 'Location: /admin/user/twofa/' ); exit; case 'user-logout': setcookie( $cookie_name, '', [ 'expires' => time() - 86400, 'path' => '/', 'domain' => $domain, 'secure' => true, 'httponly' => true, 'samesite' => 'Lax', ] ); \Shared\Helpers\Helpers::delete_session( 'twofa_pending' ); session_destroy(); header( 'Location: /admin/' ); exit; } } /** * Entry point — auth check + layout rendering. */ public static function render(): string { global $user; if ( \Shared\Helpers\Helpers::get( 'module' ) === 'user' && \Shared\Helpers\Helpers::get( 'action' ) === 'twofa' ) { $controller = self::createController( 'Users' ); return $controller->twofa(); } if ( !$user || !$user['admin'] ) { $controller = self::createController( 'Users' ); return $controller->login_form(); } $tpl = new \Shared\Tpl\Tpl; $tpl->content = self::route(); return $tpl->render( 'site/main-layout' ); } /** * Routing — buduje nazwę modułu z URL i wywołuje akcję kontrolera. */ public static function route() { $_SESSION['admin'] = true; if ( \Shared\Helpers\Helpers::get( 'p' ) ) \Shared\Helpers\Helpers::set_session( 'p', \Shared\Helpers\Helpers::get( 'p' ) ); // Budowanie nazwy modułu: shop_product → ShopProduct $moduleName = ''; $parts = explode( '_', (string) \Shared\Helpers\Helpers::get( 'module' ) ); foreach ( $parts as $part ) $moduleName .= ucfirst( $part ); $action = \Shared\Helpers\Helpers::get( 'action' ); $controller = self::createController( $moduleName ); if ( $controller && method_exists( $controller, $action ) ) return $controller->$action(); \Shared\Helpers\Helpers::alert( 'Nieprawidłowy adres url.' ); return false; } /** * Tworzy instancję kontrolera z Dependency Injection. */ private static function createController( string $moduleName ) { $factories = self::getControllerFactories(); if ( !isset( $factories[$moduleName] ) ) return null; $factory = $factories[$moduleName]; return is_callable( $factory ) ? $factory() : null; } /** * Zwraca mapę fabryk kontrolerów (lazy init). */ private static function getControllerFactories(): array { if ( !empty( self::$newControllers ) ) return self::$newControllers; self::$newControllers = [ 'Dashboard' => function() { global $mdb; return new \admin\Controllers\DashboardController( new \Domain\Dashboard\DashboardRepository( $mdb ), new \Domain\ShopStatus\ShopStatusRepository( $mdb ) ); }, 'Articles' => function() { global $mdb; return new \admin\Controllers\ArticlesController( new \Domain\Article\ArticleRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ), new \Domain\Layouts\LayoutsRepository( $mdb ), new \Domain\Pages\PagesRepository( $mdb ) ); }, 'ArticlesArchive' => function() { global $mdb; return new \admin\Controllers\ArticlesArchiveController( new \Domain\Article\ArticleRepository( $mdb ) ); }, 'Banners' => function() { global $mdb; return new \admin\Controllers\BannerController( new \Domain\Banner\BannerRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'Settings' => function() { global $mdb; return new \admin\Controllers\SettingsController( new \Domain\Settings\SettingsRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'ProductArchive' => function() { global $mdb; return new \admin\Controllers\ProductArchiveController( new \Domain\Product\ProductRepository( $mdb ) ); }, 'Archive' => function() { global $mdb; return new \admin\Controllers\ProductArchiveController( new \Domain\Product\ProductRepository( $mdb ) ); }, 'Dictionaries' => function() { global $mdb; return new \admin\Controllers\DictionariesController( new \Domain\Dictionaries\DictionariesRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'Filemanager' => function() { return new \admin\Controllers\FilemanagerController(); }, 'Users' => function() { global $mdb; return new \admin\Controllers\UsersController( new \Domain\User\UserRepository( $mdb ) ); }, 'Languages' => function() { global $mdb; return new \admin\Controllers\LanguagesController( new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'Layouts' => function() { global $mdb; return new \admin\Controllers\LayoutsController( new \Domain\Layouts\LayoutsRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'Newsletter' => function() { global $mdb; return new \admin\Controllers\NewsletterController( new \Domain\Newsletter\NewsletterRepository( $mdb, new \Domain\Settings\SettingsRepository( $mdb ) ), new \Domain\Newsletter\NewsletterPreviewRenderer() ); }, 'Scontainers' => function() { global $mdb; return new \admin\Controllers\ScontainersController( new \Domain\Scontainers\ScontainersRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'ShopPromotion' => function() { global $mdb; return new \admin\Controllers\ShopPromotionController( new \Domain\Promotion\PromotionRepository( $mdb ) ); }, 'ShopCoupon' => function() { global $mdb; return new \admin\Controllers\ShopCouponController( new \Domain\Coupon\CouponRepository( $mdb ) ); }, 'ShopAttribute' => function() { global $mdb; return new \admin\Controllers\ShopAttributeController( new \Domain\Attribute\AttributeRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'ShopPaymentMethod' => function() { global $mdb; return new \admin\Controllers\ShopPaymentMethodController( new \Domain\PaymentMethod\PaymentMethodRepository( $mdb ) ); }, 'ShopTransport' => function() { global $mdb; return new \admin\Controllers\ShopTransportController( new \Domain\Transport\TransportRepository( $mdb ), new \Domain\PaymentMethod\PaymentMethodRepository( $mdb ) ); }, 'Pages' => function() { global $mdb; return new \admin\Controllers\PagesController( new \Domain\Pages\PagesRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ), new \Domain\Layouts\LayoutsRepository( $mdb ) ); }, 'Integrations' => function() { global $mdb; return new \admin\Controllers\IntegrationsController( new \Domain\Integrations\IntegrationsRepository( $mdb ), new \Domain\Integrations\ApiloRepository( $mdb ) ); }, 'ShopStatuses' => function() { global $mdb; return new \admin\Controllers\ShopStatusesController( new \Domain\ShopStatus\ShopStatusRepository( $mdb ) ); }, 'ShopProductSets' => function() { global $mdb; return new \admin\Controllers\ShopProductSetsController( new \Domain\ProductSet\ProductSetRepository( $mdb ) ); }, 'ShopProducer' => function() { global $mdb; return new \admin\Controllers\ShopProducerController( new \Domain\Producer\ProducerRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'ShopCategory' => function() { global $mdb; return new \admin\Controllers\ShopCategoryController( new \Domain\Category\CategoryRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'ShopProduct' => function() { global $mdb; return new \admin\Controllers\ShopProductController( new \Domain\Product\ProductRepository( $mdb ), new \Domain\Integrations\IntegrationsRepository( $mdb ), new \Domain\Languages\LanguagesRepository( $mdb ) ); }, 'ShopClients' => function() { global $mdb; return new \admin\Controllers\ShopClientsController( new \Domain\Client\ClientRepository( $mdb ) ); }, 'ShopOrder' => function() { global $mdb; $productRepo = new \Domain\Product\ProductRepository( $mdb ); return new \admin\Controllers\ShopOrderController( new \Domain\Order\OrderAdminService( new \Domain\Order\OrderRepository( $mdb ), $productRepo, new \Domain\Settings\SettingsRepository( $mdb ), new \Domain\Transport\TransportRepository( $mdb ), new \Domain\CronJob\CronJobRepository( $mdb ) ), $productRepo ); }, 'Update' => function() { global $mdb; return new \admin\Controllers\UpdateController( new \Domain\Update\UpdateRepository( $mdb ) ); }, ]; return self::$newControllers; } public static function update() { global $mdb; $repository = new \Domain\Update\UpdateRepository( $mdb ); $repository->runPendingMigrations(); } }