ver. 0.277: ShopProduct factory, Dashboard, Update migration, legacy cleanup, admin\App
- ShopProduct factory: full migration (~40 ProductRepository methods, ~30 controller actions) - Dashboard: Domain+DI migration (DashboardRepository + DashboardController) - Update: Domain+DI migration (UpdateRepository + UpdateController, template rewrite) - Renamed admin\Site to admin\App, removed dead fallback routing - Removed all legacy folders: admin/controls, admin/factory, admin/view - Newsletter: switched from admin\factory\Articles to ArticleRepository - 414 tests, 1335 assertions passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
432
autoload/admin/App.php
Normal file
432
autoload/admin/App.php
Normal file
@@ -0,0 +1,432 @@
|
||||
<?php
|
||||
namespace admin;
|
||||
|
||||
class App
|
||||
{
|
||||
const APP_SECRET_KEY = 'c3cb2537d25c0efc9e573d059d79c3b8';
|
||||
|
||||
/**
|
||||
* Mapa nowych kontrolerów: module => fabryka kontrolera (DI)
|
||||
*/
|
||||
private static $newControllers = [];
|
||||
|
||||
public static function finalize_admin_login( array $user, string $domain, string $cookie_name, bool $remember = false )
|
||||
{
|
||||
\S::set_session( 'user', $user );
|
||||
\S::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 = \S::get( 's-action' );
|
||||
if ( !$sa ) return;
|
||||
|
||||
$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 = \S::get( 'login' );
|
||||
$pass = \S::get( 'password' );
|
||||
$result = $users->logon( $login, $pass );
|
||||
|
||||
if ( $result == 1 )
|
||||
{
|
||||
$user = $users->details( $login );
|
||||
|
||||
if ( $user['twofa_enabled'] == 1 )
|
||||
{
|
||||
\S::set_session( 'twofa_pending', [
|
||||
'uid' => (int) $user['id'],
|
||||
'login' => $login,
|
||||
'remember' => (bool) \S::get( 'remember' ),
|
||||
'started' => time(),
|
||||
] );
|
||||
|
||||
if ( !$users->sendTwofaCode( (int) $user['id'] ) )
|
||||
{
|
||||
\S::alert( 'Nie udało się wysłać kodu 2FA. Spróbuj ponownie.' );
|
||||
\S::delete_session( 'twofa_pending' );
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
header( 'Location: /admin/user/twofa/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
self::finalize_admin_login( $user, $domain, $cookie_name, (bool) \S::get( 'remember' ) );
|
||||
header( 'Location: /admin/articles/list/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $result == -1 )
|
||||
\S::alert( 'Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.' );
|
||||
else
|
||||
\S::alert( 'Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.' );
|
||||
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
|
||||
case 'user-2fa-verify':
|
||||
$pending = \S::get_session( 'twofa_pending' );
|
||||
if ( !$pending || empty( $pending['uid'] ) ) {
|
||||
\S::alert( 'Sesja 2FA wygasła. Zaloguj się ponownie.' );
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$code = trim( (string) \S::get( 'twofa' ) );
|
||||
if ( !preg_match( '/^\d{6}$/', $code ) )
|
||||
{
|
||||
\S::alert( 'Nieprawidłowy format kodu.' );
|
||||
header( 'Location: /admin/user/twofa/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( !$users->verifyTwofaCode( (int) $pending['uid'], $code ) )
|
||||
{
|
||||
\S::alert( 'Błędny lub wygasły kod.' );
|
||||
header( 'Location: /admin/user/twofa/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$user = $users->details( $pending['login'] );
|
||||
self::finalize_admin_login( $user, $domain, $cookie_name, !empty( $pending['remember'] ) );
|
||||
header( 'Location: /admin/articles/list/' );
|
||||
exit;
|
||||
|
||||
case 'user-2fa-resend':
|
||||
$pending = \S::get_session( 'twofa_pending' );
|
||||
if ( !$pending || empty( $pending['uid'] ) )
|
||||
{
|
||||
\S::alert( 'Sesja 2FA wygasła. Zaloguj się ponownie.' );
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( !$users->sendTwofaCode( (int) $pending['uid'], true ) )
|
||||
\S::alert( 'Kod można wysłać ponownie po krótkiej przerwie.' );
|
||||
else
|
||||
\S::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',
|
||||
] );
|
||||
\S::delete_session( 'twofa_pending' );
|
||||
session_destroy();
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point — auth check + layout rendering.
|
||||
*/
|
||||
public static function render(): string
|
||||
{
|
||||
global $user;
|
||||
|
||||
if ( \S::get( 'module' ) === 'user' && \S::get( 'action' ) === 'twofa' ) {
|
||||
$controller = self::createController( 'Users' );
|
||||
return $controller->twofa();
|
||||
}
|
||||
|
||||
if ( !$user || !$user['admin'] )
|
||||
{
|
||||
$controller = self::createController( 'Users' );
|
||||
return $controller->login_form();
|
||||
}
|
||||
|
||||
$tpl = new \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 ( \S::get( 'p' ) )
|
||||
\S::set_session( 'p', \S::get( 'p' ) );
|
||||
|
||||
// Budowanie nazwy modułu: shop_product → ShopProduct
|
||||
$moduleName = '';
|
||||
$parts = explode( '_', (string) \S::get( 'module' ) );
|
||||
foreach ( $parts as $part )
|
||||
$moduleName .= ucfirst( $part );
|
||||
|
||||
$action = \S::get( 'action' );
|
||||
|
||||
$controller = self::createController( $moduleName );
|
||||
if ( $controller && method_exists( $controller, $action ) )
|
||||
return $controller->$action();
|
||||
|
||||
\S::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 )
|
||||
);
|
||||
},
|
||||
'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 )
|
||||
);
|
||||
},
|
||||
'ShopClients' => function() {
|
||||
global $mdb;
|
||||
return new \admin\Controllers\ShopClientsController(
|
||||
new \Domain\Client\ClientRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopOrder' => function() {
|
||||
global $mdb;
|
||||
return new \admin\Controllers\ShopOrderController(
|
||||
new \Domain\Order\OrderAdminService(
|
||||
new \Domain\Order\OrderRepository( $mdb )
|
||||
)
|
||||
);
|
||||
},
|
||||
'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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user