Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
95.51% |
596 / 624 |
|
80.00% |
24 / 30 |
CRAP | |
0.00% |
0 / 1 |
| ApplicationBootstrap | |
95.51% |
595 / 623 |
|
80.00% |
24 / 30 |
63 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| bootstrapCoreSubsystems | |
100.00% |
75 / 75 |
|
100.00% |
1 / 1 |
2 | |||
| bootstrapApplicationRuntime | |
100.00% |
107 / 107 |
|
100.00% |
1 / 1 |
1 | |||
| getContainer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| handle | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
1 | |||
| dispatch | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| handleSessionSecurityValidation | |
42.86% |
3 / 7 |
|
0.00% |
0 / 1 |
12.72 | |||
| validateActiveSession | |
92.59% |
25 / 27 |
|
0.00% |
0 / 1 |
7.02 | |||
| checkImpersonationLifetime | |
16.67% |
2 / 12 |
|
0.00% |
0 / 1 |
19.47 | |||
| clearUserSession | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
| validateLegacySession | n/a |
0 / 0 |
n/a |
0 / 0 |
3 | |||||
| getCentralEngineApiController | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getPsr17Factory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getSession | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| initializeDatabase | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| initializeCache | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
4.25 | |||
| createRouteDispatcher | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
| initializeCoreEnvironment | |
100.00% |
37 / 37 |
|
100.00% |
1 / 1 |
2 | |||
| initializeSecurityAndTranslation | |
100.00% |
48 / 48 |
|
100.00% |
1 / 1 |
1 | |||
| initializeDashboardSubsystem | |
100.00% |
35 / 35 |
|
100.00% |
1 / 1 |
1 | |||
| initializeClientUserRepositories | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
| initializeFeatureControllers | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
1 | |||
| createApiDispatcher | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
1 | |||
| initializeEngineSubsystem | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| assembleApis | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| buildImpersonationSubsystem | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
1 | |||
| buildRouteDispatcherOptions | |
100.00% |
38 / 38 |
|
100.00% |
1 / 1 |
1 | |||
| buildContainerServices | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
1 | |||
| buildApplicationContainer | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
1 | |||
| createCoreDefinitions | |
100.00% |
55 / 55 |
|
100.00% |
1 / 1 |
1 | |||
| loadDotEnv | |
23.08% |
3 / 13 |
|
0.00% |
0 / 1 |
45.87 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | /** @license For full copyright and license information, please see the LICENSE.md file. */ |
| 6 | |
| 7 | namespace App\Core\Bootstrap; |
| 8 | |
| 9 | defined('AMMONLY_APP') || exit('Direct script access is forbidden.'); |
| 10 | |
| 11 | use App\Core\Access\Application\Service\AccessManagerService; |
| 12 | use App\Core\Access\Domain\Repository\AccessRepositoryInterface; |
| 13 | use App\Core\Access\Infrastructure\Cache\CachedAccessRepository; |
| 14 | use App\Core\Access\Infrastructure\Repository\SqlAccessRepository; |
| 15 | use App\Core\Access\Presentation\Api\AccessApiController; |
| 16 | use App\Core\Access\Presentation\Web\AccessWebController; |
| 17 | use App\Modules\Profiles\Application\Service\ProfilePermissionService; |
| 18 | use App\Modules\Profiles\Domain\Repository\ProfilePermissionRepositoryInterface; |
| 19 | use App\Modules\Profiles\Infrastructure\Repository\CachedProfilePermissionRepository; |
| 20 | use App\Modules\Profiles\Infrastructure\Repository\SqlProfilePermissionRepository; |
| 21 | use App\Modules\Profiles\Presentation\Api\ProfilePermissionApiController; |
| 22 | use App\Core\Api\Domain\Repository\ApiLoggerInterface; |
| 23 | use App\Core\Api\Domain\Service\PayloadSanitizer; |
| 24 | use App\Core\Api\Infrastructure\Repository\SqlApiKeyRepository; |
| 25 | use App\Core\Api\Infrastructure\Repository\SqlApiLoggerRepository; |
| 26 | use App\Core\Api\Middleware\ApiLoggingMiddleware; |
| 27 | use App\Core\Api\Middleware\ApiPayloadGuardMiddleware; |
| 28 | use App\Core\Api\Middleware\ApiTokenMiddleware; |
| 29 | use App\Core\Api\Middleware\CorsMiddleware; |
| 30 | use App\Core\Api\Middleware\IdempotencyMiddleware; |
| 31 | use App\Core\Api\Middleware\RequestIdMiddleware; |
| 32 | use App\Core\Api\Presentation\ApiDispatcher; |
| 33 | use App\Core\Api\Presentation\Web\OpenApiDocController; |
| 34 | use App\Core\Audit\Application\Service\AuditIntegrityService; |
| 35 | use App\Core\Audit\Application\Service\AuditRetentionService; |
| 36 | use App\Core\Audit\Application\Service\SecurityAuditLogger; |
| 37 | use App\Core\Audit\Presentation\Api\AuditApiController; |
| 38 | use App\Core\Engine\Domain\Repository\AuditRepositoryInterface; |
| 39 | use App\Core\Bootstrap\Configurator\BootstrapApiDispatcherFactory; |
| 40 | use App\Core\Bootstrap\Configurator\BootstrapEngineContext; |
| 41 | use App\Core\Bootstrap\Configurator\BootstrapEngineFactory; |
| 42 | use App\Core\Bootstrap\Configurator\BootstrapRouteDispatcher; |
| 43 | use App\Core\Bootstrap\Configurator\BootstrapRouteDispatcherFactory; |
| 44 | use App\Core\Bootstrap\Provider\CommerceBootstrapProvider; |
| 45 | use App\Core\Bootstrap\Provider\EngineComponentsBootstrapProvider; |
| 46 | use App\Core\Bootstrap\Provider\PlatformFeaturesBootstrapProvider; |
| 47 | use App\Core\Bootstrap\Provider\PlatformServicesBootstrapProvider; |
| 48 | use App\Core\Bootstrap\Provider\UserAndAuthBootstrapProvider; |
| 49 | use App\Core\Bootstrap\Configurator\ContainerConfigurator; |
| 50 | use App\Core\Bootstrap\Configurator\SecurityAndTemplateConfigurator; |
| 51 | use App\Core\Bootstrap\Configurator\TranslationAndMenuConfigurator; |
| 52 | use App\Core\Cron\Presentation\Api\CronApiController; |
| 53 | use App\Core\Database\MultiDbRouter; |
| 54 | use App\Core\Engine\Application\Security\PermissionContextFactory; |
| 55 | use App\Core\Engine\Application\Service\UniversalCrudService; |
| 56 | use App\Modules\Currencies\Presentation\Api\CurrenciesApiController; |
| 57 | use App\Modules\Currencies\Presentation\Web\CurrenciesWebController; |
| 58 | use App\Modules\Dashboard\Application\Service\DashboardCalendarActionService; |
| 59 | use App\Modules\Dashboard\Application\Service\DashboardWidgetsService; |
| 60 | use App\Modules\Dashboard\Presentation\Api\DashboardApiController; |
| 61 | use App\Modules\Dashboard\Presentation\Htmx\DashboardHtmxController; |
| 62 | use App\Modules\Dashboard\Presentation\Web\DashboardWebController; |
| 63 | use App\Modules\Discount\Application\Service\DiscountApplicationService; |
| 64 | use App\Modules\Discount\Infrastructure\Repository\SqlDiscountRepository; |
| 65 | use App\Modules\Discount\Presentation\Api\DiscountRatesApiController; |
| 66 | use App\Modules\Discount\Presentation\Htmx\DiscountHtmxController; |
| 67 | use App\Modules\Discount\Presentation\Web\DiscountSettingsWebController; |
| 68 | use App\Core\Engine\Application\Service\DataMappingApplicationService; |
| 69 | use App\Core\Engine\Application\Service\InventoryApplicationService; |
| 70 | use App\Core\Engine\Application\Service\InventorySchemaEngine; |
| 71 | use App\Core\Engine\Application\Service\UniversalCrudServiceInterface; |
| 72 | use App\Core\Engine\Infrastructure\Repository\SqlDataMappingRepository; |
| 73 | use App\Core\Engine\Infrastructure\Repository\SqlInventoryFieldRepository; |
| 74 | use App\Core\Engine\Infrastructure\Repository\SqlInventoryRecordRepository; |
| 75 | use App\Core\Engine\Presentation\Api\InventoryFieldsApiController; |
| 76 | use App\Modules\DataMapping\Presentation\Htmx\DataMappingHtmxController; |
| 77 | use App\Modules\DataMapping\Presentation\Web\DataMappingWebController; |
| 78 | use App\Modules\Inventory\Presentation\Htmx\InventoryItemsHtmxController; |
| 79 | use App\Modules\Inventory\Presentation\Htmx\InventorySettingsHtmxController; |
| 80 | use App\Modules\Inventory\Presentation\Web\InventorySettingsWebController; |
| 81 | use App\Modules\Quotes\Presentation\Web\QuoteConversionWebController; |
| 82 | use App\Modules\Tax\Application\Service\TaxApplicationService; |
| 83 | use App\Modules\Tax\Domain\Service\TaxDeterminationMatrix; |
| 84 | use App\Modules\Tax\Domain\Service\TaxEngine; |
| 85 | use App\Modules\Tax\Infrastructure\Repository\SqlTaxGroupRepository; |
| 86 | use App\Modules\Tax\Infrastructure\Repository\SqlTaxRateRepository; |
| 87 | use App\Modules\Tax\Infrastructure\Repository\SqlTaxRuleRepository; |
| 88 | use App\Modules\Tax\Presentation\Api\TaxCalculateApiController; |
| 89 | use App\Modules\Tax\Presentation\Api\TaxGroupsApiController; |
| 90 | use App\Modules\Tax\Presentation\Api\TaxRatesApiController; |
| 91 | use App\Modules\Tax\Presentation\Api\TaxRulesApiController; |
| 92 | use App\Modules\Tax\Presentation\Htmx\TaxHtmxController; |
| 93 | use App\Modules\Tax\Presentation\Web\TaxSettingsWebController; |
| 94 | use App\Core\Engine\Domain\Repository\MetadataRepositoryInterface; |
| 95 | use App\Core\Engine\Infrastructure\Cache\PersistentMetadataCache; |
| 96 | use App\Core\Engine\Infrastructure\Cache\RequestScopedMetadataCache; |
| 97 | use App\Core\Engine\Infrastructure\Repository\SqlAuditRepository; |
| 98 | use App\Core\Engine\Infrastructure\Repository\SqlMetadataRepository; |
| 99 | use App\Core\Engine\Infrastructure\Settings\EngineSettings; |
| 100 | use App\Core\Engine\Presentation\Api\CentralEngineApiController; |
| 101 | use App\Core\Engine\Presentation\Web\CentralEngineWebController; |
| 102 | use App\Core\Error\CentralErrorHandler; |
| 103 | use App\Core\Event\SystemEventDispatcher; |
| 104 | use App\Modules\Automation\Application\Listener\WorkflowRecordLifecycleListener; |
| 105 | use App\Modules\Automation\Application\Service\WorkflowDagEngine; |
| 106 | use App\Modules\Automation\Infrastructure\Repository\SqlWorkflowRepository; |
| 107 | use App\Core\Instance\Application\Service\InstanceContextManager; |
| 108 | use App\Core\Instance\Application\Service\InstanceContextManagerInterface; |
| 109 | use App\Core\Instance\Application\Service\RemoteInstanceEngineGateway; |
| 110 | use App\Core\Instance\Infrastructure\Client\RemoteInstanceApiClient; |
| 111 | use App\Core\Instance\Infrastructure\Repository\SqlClientInstanceRepository; |
| 112 | use App\Core\Instance\Presentation\Api\InstanceContextApiController; |
| 113 | use App\Core\Instance\Presentation\Twig\TwigInstanceContextExtension; |
| 114 | use App\Core\Layout\Presentation\Api\LayoutApiController; |
| 115 | use App\Core\Search\Application\Service\GlobalSearchService; |
| 116 | use App\Core\Search\Application\Service\SearchIndexOptimizer; |
| 117 | use App\Core\Search\Application\Service\SearchQueryParser; |
| 118 | use App\Core\Search\Infrastructure\Repository\SqlSearchRepository; |
| 119 | use App\Core\Search\Presentation\Api\GlobalSearchApiController; |
| 120 | use App\Core\Search\Presentation\Api\SearchSettingsApiController; |
| 121 | use App\Core\Preference\Application\Command\ResetUserPreferencesHandler; |
| 122 | use App\Core\Preference\Application\Command\SaveUserPreferenceHandler; |
| 123 | use App\Core\Preference\Domain\Service\ConfigurationCascadeResolver; |
| 124 | use App\Core\Preference\Infrastructure\Repository\SqlUserPreferenceRepository; |
| 125 | use App\Core\Preference\Presentation\Api\UserPreferencesApiController; |
| 126 | use App\Core\Preference\Presentation\Twig\UserPreferencesTwigExtension; |
| 127 | use App\Core\Profile\Application\Service\ProfileManager; |
| 128 | use App\Core\Profile\Domain\Model\Profile; |
| 129 | use App\Core\Security\Encryption\AesGcmEncryptionService; |
| 130 | use App\Core\Security\Middleware\RateLimiterMiddleware; |
| 131 | use App\Core\Security\Middleware\SecurityHeadersMiddleware; |
| 132 | use App\Core\Settings\Presentation\Api\SettingsApiController; |
| 133 | use App\Core\Settings\SqlSettingsRepository; |
| 134 | use App\Core\Translation\Middleware\LocaleDetectionMiddleware; |
| 135 | use App\Core\Translation\Presentation\Api\TranslationApiController; |
| 136 | use App\Modules\About\Application\DataSource\AboutLicensesDataProvider; |
| 137 | use App\Modules\About\Application\DataSource\AboutRequirementsDataProvider; |
| 138 | use App\Modules\About\Application\Service\SystemDiagnosticsService; |
| 139 | use App\Core\Engine\Infrastructure\Repository\SqlRecordCoOwnerRepository; |
| 140 | use App\Modules\About\Infrastructure\Repository\SqlSystemRequirementsRepository; |
| 141 | use App\Modules\About\Presentation\Api\AboutLicensesApiController; |
| 142 | use App\Modules\About\Presentation\Api\AboutRequirementsApiController; |
| 143 | use App\Modules\Comments\Application\Service\CommentAttachmentService; |
| 144 | use App\Modules\Comments\Application\Service\CommentHierarchyRollupService; |
| 145 | use App\Modules\Comments\Application\Service\CommentMentionParserService; |
| 146 | use App\Modules\Comments\Application\Service\CommentMentionsQueryService; |
| 147 | use App\Modules\Comments\Application\Service\CommentService; |
| 148 | use App\Modules\Comments\Infrastructure\Repository\SqlCommentRepository; |
| 149 | use App\Modules\Comments\Presentation\Api\CommentsApiController; |
| 150 | use App\Modules\Comments\Presentation\Htmx\CommentsHtmxController; |
| 151 | use App\Modules\Comments\Presentation\Htmx\MentionsHtmxController; |
| 152 | use App\Modules\Comments\Presentation\Web\CommentAttachmentDownloadWebController; |
| 153 | use App\Modules\Comments\Presentation\Web\MentionsWebController; |
| 154 | use App\Modules\Dav\Infrastructure\Config\AppleMobileConfigGenerator; |
| 155 | use App\Modules\Dav\Infrastructure\Repository\SqlDavDeviceRepository; |
| 156 | use App\Modules\Dav\Presentation\Api\DavDeviceApiController; |
| 157 | use App\Modules\Dav\Presentation\Web\DavSetupWebController; |
| 158 | use App\Modules\Logs\Presentation\Api\JsErrorLogApiController; |
| 159 | use App\Modules\Mail\Application\Service\MailHtmlSanitizer; |
| 160 | use App\Modules\Mail\Application\Service\MailDateFormatService; |
| 161 | use App\Modules\Mail\Application\Service\MailSenderService; |
| 162 | use App\Modules\Mail\Application\Service\MailTemplateRenderer; |
| 163 | use App\Modules\Mail\Application\Service\WebmailAuthService; |
| 164 | use App\Modules\Mail\Application\Service\WebmailFolderService; |
| 165 | use App\Modules\Mail\Application\Service\EmailContextAssociationService; |
| 166 | use App\Modules\Mail\Application\Service\WebmailMessageService; |
| 167 | use App\Modules\Mail\Application\Service\WebmailSenderService; |
| 168 | use App\Modules\Mail\Application\Service\WebmailSyncService; |
| 169 | use App\Modules\Mail\Infrastructure\Protocol\MailProtocolDriverRegistry; |
| 170 | use App\Modules\Mail\Infrastructure\Repository\SqlMailRepository; |
| 171 | use App\Modules\Mail\Presentation\Api\MailApiController; |
| 172 | use App\Modules\Mail\Presentation\Api\WebmailApiController; |
| 173 | use App\Modules\Mail\Presentation\Api\WebmailEventsApiController; |
| 174 | use App\Modules\Mail\Presentation\Htmx\WebmailHtmxController; |
| 175 | use App\Modules\Mail\Presentation\Web\WebmailWebController; |
| 176 | use App\Modules\Map\Application\Service\IpGeolocationService; |
| 177 | use App\Modules\Map\Application\Service\MapGeocodingService; |
| 178 | use App\Modules\Map\Application\Service\MapRoutingService; |
| 179 | use App\Core\Template\Application\Service\TemplateCompilerService; |
| 180 | use App\Core\Template\Application\Service\TemplateVariableResolver; |
| 181 | use App\Modules\Pdf\Application\Service\PdfGeneratorService; |
| 182 | use App\Modules\Pdf\Infrastructure\Driver\MpdfRendererDriver; |
| 183 | use App\Modules\Pdf\Infrastructure\Repository\SqlPdfTemplateRepository; |
| 184 | use App\Modules\Pdf\Infrastructure\Repository\SqlPdfTemplateVersionRepository; |
| 185 | use App\Modules\Pdf\Presentation\Api\PdfApiController; |
| 186 | use App\Modules\Pdf\Presentation\Htmx\PdfHtmxController; |
| 187 | use App\Modules\Pdf\Presentation\Htmx\PdfQueueHtmxController; |
| 188 | use App\Modules\Pdf\Presentation\Htmx\PdfSignatureHtmxController; |
| 189 | use App\Modules\Structure\Application\Service\StructureReassignmentService; |
| 190 | use App\Modules\Structure\Application\Service\StructureTreeBuilder; |
| 191 | use App\Modules\Structure\Infrastructure\Repository\SqlStructureMembershipRepository; |
| 192 | use App\Modules\Structure\Infrastructure\Repository\SqlStructureRepository; |
| 193 | use App\Modules\Structure\Presentation\Api\OwnershipApiController; |
| 194 | use App\Modules\Structure\Presentation\Api\StructureApiController; |
| 195 | use App\Modules\Pdf\Presentation\Web\DocumentVerificationWebController; |
| 196 | use App\Modules\Pdf\Presentation\Web\PdfWebController; |
| 197 | use App\Modules\Mail\Application\Service\MailTrackingService; |
| 198 | use App\Modules\Mail\Presentation\Web\MailTemplateWebController; |
| 199 | use App\Modules\Mail\Presentation\Web\MailTrackingWebController; |
| 200 | use App\Modules\Map\Domain\Repository\MapPointRepositoryInterface; |
| 201 | use App\Modules\Map\Infrastructure\Client\AmmonlyMapApiClient; |
| 202 | use App\Modules\Map\Infrastructure\GeoIp\MaxMindMmdbReader; |
| 203 | use App\Modules\Map\Infrastructure\Provider\AmmonlyLocalMapProvider; |
| 204 | use App\Modules\Map\Infrastructure\Provider\MapTilerMapProvider; |
| 205 | use App\Modules\Map\Infrastructure\Provider\SmartMapLoadBalancer; |
| 206 | use App\Modules\Map\Presentation\Api\MapApiController; |
| 207 | use App\Modules\Map\Presentation\Htmx\MapHtmxController; |
| 208 | use App\Modules\Map\Presentation\Web\MapWebController; |
| 209 | use App\Modules\Menu\Presentation\Api\MenuApiController; |
| 210 | use App\Modules\User\Application\Service\UserImpersonationService; |
| 211 | use App\Modules\User\Application\Service\UserProfileService; |
| 212 | use App\Modules\User\Infrastructure\Repository\SqlUserImpersonationRepository; |
| 213 | use App\Modules\User\Infrastructure\Repository\SqlUserRepository; |
| 214 | use App\Modules\User\Infrastructure\Repository\UserIdentityRepository; |
| 215 | use App\Modules\User\Presentation\Web\ImpersonationWebController; |
| 216 | use App\Core\Time\NtpTimeSyncService; |
| 217 | use App\Modules\Mail\Application\Service\SystemMailSenderService; |
| 218 | use App\Modules\User\Application\Service\PasswordResetService; |
| 219 | use App\Modules\User\Application\Service\UserMfaDeviceService; |
| 220 | use App\Modules\User\Presentation\Api\AuthApiController; |
| 221 | use App\Modules\User\Presentation\Api\LocaleApiController; |
| 222 | use App\Modules\User\Presentation\Api\MfaApiController; |
| 223 | use App\Modules\User\Presentation\Api\UserProfileApiController; |
| 224 | use App\Modules\User\Presentation\Middleware\AuthenticatedOnlyMiddleware; |
| 225 | use App\Modules\User\Presentation\Middleware\GuestOnlyMiddleware; |
| 226 | use App\Modules\User\Presentation\Web\AuthController; |
| 227 | use App\Modules\User\Presentation\Web\Identity; |
| 228 | use App\Shared\Infrastructure\Config\InstallerConfigLoader; |
| 229 | use Nyholm\Psr7\Factory\Psr17Factory; |
| 230 | use PDO; |
| 231 | use Psr\Container\ContainerInterface; |
| 232 | use Psr\Http\Message\ResponseInterface; |
| 233 | use Psr\Http\Message\ServerRequestInterface; |
| 234 | use Psr\Http\Server\RequestHandlerInterface; |
| 235 | use Yiisoft\Cache\ArrayCache; |
| 236 | use Yiisoft\Cache\Cache; |
| 237 | use Yiisoft\Cache\CacheInterface; |
| 238 | use Yiisoft\Cache\File\FileCache; |
| 239 | use Yiisoft\Session\SessionInterface; |
| 240 | use Yiisoft\Session\SessionMiddleware; |
| 241 | use Yiisoft\User\CurrentUser; |
| 242 | |
| 243 | /** |
| 244 | * Application Bootstrap. |
| 245 | * |
| 246 | * Bootstraps and coordinates the application lifecycle, dependency resolution, |
| 247 | * middleware execution pipeline, and HTTP response delivery with API-First design. |
| 248 | * |
| 249 | * @package App\Core\Bootstrap |
| 250 | */ |
| 251 | final class ApplicationBootstrap |
| 252 | { |
| 253 | private ContainerInterface $container; |
| 254 | private Psr17Factory $psr17Factory; |
| 255 | private SessionInterface $session; |
| 256 | private CurrentUser $currentUser; |
| 257 | private SqlUserRepository $userRepository; |
| 258 | private CentralEngineApiController $centralEngineApiController; |
| 259 | private SecurityHeadersMiddleware $securityHeadersMiddleware; |
| 260 | private SessionMiddleware $sessionMiddleware; |
| 261 | private const string LOGS_SUBPATH = '/storage/logs'; |
| 262 | |
| 263 | private LocaleDetectionMiddleware $localeDetectionMiddleware; |
| 264 | private BootstrapRouteDispatcher $routeDispatcher; |
| 265 | private ?SqlUserRepository $clientUserRepository = null; |
| 266 | |
| 267 | /** |
| 268 | * ApplicationBootstrap constructor. |
| 269 | * |
| 270 | * @param string $basePath Base application filesystem directory. |
| 271 | * @param PDO|null $pdo Optional database connection instance. |
| 272 | */ |
| 273 | public function __construct(string $basePath, ?PDO $pdo = null) |
| 274 | { |
| 275 | [$env, $secContext, $engineContext] = $this->bootstrapCoreSubsystems($basePath, $pdo); |
| 276 | $this->bootstrapApplicationRuntime($basePath, $env, $secContext, $engineContext); |
| 277 | } |
| 278 | |
| 279 | /** |
| 280 | * Bootstraps core database, security, and engine subsystems. |
| 281 | * |
| 282 | * @param string $basePath Base application filesystem path. |
| 283 | * @param PDO|null $pdo Optional database connection. |
| 284 | * @return array{0: array<string, mixed>, 1: array<string, mixed>, 2: array<string, mixed>} |
| 285 | */ |
| 286 | private function bootstrapCoreSubsystems(string $basePath, ?PDO $pdo): array |
| 287 | { |
| 288 | [ |
| 289 | $pdo, |
| 290 | $prefix, |
| 291 | $sec, |
| 292 | $cache, |
| 293 | $cacheHandler, |
| 294 | $metadataRepo, |
| 295 | $instanceContextManager, |
| 296 | $remoteEngineGateway, |
| 297 | ] = $this->initializeCoreEnvironment($basePath, $pdo); |
| 298 | |
| 299 | [ |
| 300 | $i18n, |
| 301 | $authController, |
| 302 | $authApiController, |
| 303 | $mfaApiController, |
| 304 | $localeApiController, |
| 305 | $accessRepo, |
| 306 | $profileRepo, |
| 307 | $auditRepo, |
| 308 | $eventDispatcher, |
| 309 | ] = $this->initializeSecurityAndTranslation($basePath, $pdo, $prefix, $sec, [ |
| 310 | 'metadataRepo' => $metadataRepo, |
| 311 | 'instanceContextManager' => $instanceContextManager, |
| 312 | 'cache' => $cache, |
| 313 | ]); |
| 314 | |
| 315 | $userPrefix = ($sec->activeProfile->name === 'client') ? 'c_' : $prefix; |
| 316 | [$clientUserRepo, $clientImpersonationRepo] = $this->initializeClientUserRepositories($pdo, $sec); |
| 317 | |
| 318 | [$engineFactory, $licensesApi, $requirementsApi] = $this->initializeEngineSubsystem( |
| 319 | $pdo, |
| 320 | $prefix, |
| 321 | $basePath, |
| 322 | $sec, |
| 323 | [ |
| 324 | 'audit_repo' => $auditRepo, |
| 325 | 'metadata_repo' => $metadataRepo, |
| 326 | 'cache' => $cache, |
| 327 | 'event_dispatcher' => $eventDispatcher, |
| 328 | 'instance_context_manager' => $instanceContextManager, |
| 329 | 'remote_engine_gateway' => $remoteEngineGateway, |
| 330 | 'access_repo' => $accessRepo, |
| 331 | 'profile_repo' => $profileRepo, |
| 332 | 'client_pdo' => $pdo, |
| 333 | 'translator' => $i18n->translator, |
| 334 | ] |
| 335 | ); |
| 336 | |
| 337 | $env = [ |
| 338 | 'pdo' => $pdo, |
| 339 | 'prefix' => $prefix, |
| 340 | 'userPrefix' => $userPrefix, |
| 341 | 'sec' => $sec, |
| 342 | 'cache' => $cache, |
| 343 | 'cacheHandler' => $cacheHandler, |
| 344 | 'metadataRepo' => $metadataRepo, |
| 345 | 'instanceContextManager' => $instanceContextManager, |
| 346 | 'remoteGateway' => $remoteEngineGateway, |
| 347 | ]; |
| 348 | |
| 349 | $secContext = [ |
| 350 | 'i18n' => $i18n, |
| 351 | 'authController' => $authController, |
| 352 | 'authApiController' => $authApiController, |
| 353 | 'mfaApiController' => $mfaApiController, |
| 354 | 'localeApiController' => $localeApiController, |
| 355 | 'accessRepo' => $accessRepo, |
| 356 | 'profileRepo' => $profileRepo, |
| 357 | 'auditRepo' => $auditRepo, |
| 358 | 'eventDispatcher' => $eventDispatcher, |
| 359 | ]; |
| 360 | |
| 361 | $engineContext = [ |
| 362 | 'engineFactory' => $engineFactory, |
| 363 | 'licensesApi' => $licensesApi, |
| 364 | 'requirementsApi' => $requirementsApi, |
| 365 | 'clientUserRepo' => $clientUserRepo, |
| 366 | 'clientImpersonationRepo' => $clientImpersonationRepo, |
| 367 | ]; |
| 368 | |
| 369 | return [$env, $secContext, $engineContext]; |
| 370 | } |
| 371 | |
| 372 | /** |
| 373 | * Bootstraps features, commerce, route dispatcher, and application container. |
| 374 | * |
| 375 | * @param string $basePath Base application filesystem path. |
| 376 | * @param array<string, mixed> $env Core environment configuration. |
| 377 | * @param array<string, mixed> $secContext Security and translation services. |
| 378 | * @param array<string, mixed> $engineContext Engine subsystem and repositories. |
| 379 | */ |
| 380 | private function bootstrapApplicationRuntime( |
| 381 | string $basePath, |
| 382 | array $env, |
| 383 | array $secContext, |
| 384 | array $engineContext |
| 385 | ): void { |
| 386 | /** @var PDO $pdo */ |
| 387 | $pdo = $env['pdo']; |
| 388 | /** @var string $prefix */ |
| 389 | $prefix = $env['prefix']; |
| 390 | /** @var SecurityAndTemplateConfigurator $sec */ |
| 391 | $sec = $env['sec']; |
| 392 | /** @var TranslationAndMenuConfigurator $i18n */ |
| 393 | $i18n = $secContext['i18n']; |
| 394 | /** @var InstanceContextManager $instanceContextManager */ |
| 395 | $instanceContextManager = $env['instanceContextManager']; |
| 396 | |
| 397 | $featureSetup = $this->initializeFeatureControllers( |
| 398 | $pdo, |
| 399 | $prefix, |
| 400 | $sec, |
| 401 | $i18n, |
| 402 | $instanceContextManager, |
| 403 | [ |
| 404 | 'cache' => $env['cache'], |
| 405 | 'accessRepo' => $secContext['accessRepo'], |
| 406 | 'metadataRepo' => $env['metadataRepo'], |
| 407 | 'profileRepo' => $secContext['profileRepo'], |
| 408 | 'basePath' => $basePath, |
| 409 | ] |
| 410 | ); |
| 411 | |
| 412 | $dashboard = $this->initializeDashboardSubsystem( |
| 413 | $pdo, |
| 414 | $prefix, |
| 415 | (string) $env['userPrefix'], |
| 416 | $sec, |
| 417 | $instanceContextManager |
| 418 | ); |
| 419 | |
| 420 | $commerceProvider = new CommerceBootstrapProvider($this->psr17Factory); |
| 421 | $commerceSetup = $commerceProvider->registerAll( |
| 422 | $pdo, |
| 423 | $prefix, |
| 424 | $sec, |
| 425 | $env['metadataRepo'], |
| 426 | $this->centralEngineApiController->getCrudService(), |
| 427 | new PermissionContextFactory($this->session, $pdo), |
| 428 | [ |
| 429 | 'instanceContextManager' => $instanceContextManager, |
| 430 | 'clientPdo' => $pdo, |
| 431 | 'remoteGateway' => $env['remoteGateway'], |
| 432 | ] |
| 433 | ); |
| 434 | |
| 435 | $apis = $this->assembleApis( |
| 436 | [ |
| 437 | 'auth' => $secContext['authApiController'], |
| 438 | 'mfa' => $secContext['mfaApiController'], |
| 439 | 'locale' => $secContext['localeApiController'], |
| 440 | 'licenses' => $engineContext['licensesApi'], |
| 441 | 'requirements' => $engineContext['requirementsApi'], |
| 442 | 'dashboard' => $dashboard['api'], |
| 443 | ], |
| 444 | $featureSetup['apis'], |
| 445 | $commerceSetup['apis'] |
| 446 | ); |
| 447 | |
| 448 | $apiDispatcher = $this->createApiDispatcher( |
| 449 | $apis, |
| 450 | $i18n, |
| 451 | $engineContext['engineFactory'], |
| 452 | $sec, |
| 453 | $env['cacheHandler'], |
| 454 | $prefix, |
| 455 | $pdo |
| 456 | ); |
| 457 | |
| 458 | [$impersonationService, $impersonationWebController] = $this->buildImpersonationSubsystem( |
| 459 | $pdo, |
| 460 | $prefix, |
| 461 | $sec, |
| 462 | $engineContext['clientUserRepo'], |
| 463 | $engineContext['clientImpersonationRepo'], |
| 464 | $instanceContextManager |
| 465 | ); |
| 466 | |
| 467 | $dispatcherOptions = $this->buildRouteDispatcherOptions( |
| 468 | $env['metadataRepo'], |
| 469 | $instanceContextManager, |
| 470 | $featureSetup, |
| 471 | $commerceSetup, |
| 472 | $sec, |
| 473 | $impersonationWebController, |
| 474 | $dashboard |
| 475 | ); |
| 476 | |
| 477 | $this->routeDispatcher = $this->createRouteDispatcher( |
| 478 | $sec, |
| 479 | $basePath, |
| 480 | $secContext['authController'], |
| 481 | $apiDispatcher, |
| 482 | $engineContext['engineFactory'], |
| 483 | $dispatcherOptions |
| 484 | ); |
| 485 | |
| 486 | $containerServices = $this->buildContainerServices( |
| 487 | [ |
| 488 | 'sec' => $sec, |
| 489 | 'prefix' => $prefix, |
| 490 | 'apis' => $apis, |
| 491 | 'profile_repo' => $secContext['profileRepo'], |
| 492 | 'impersonation_service' => $impersonationService, |
| 493 | 'impersonation_web' => $impersonationWebController, |
| 494 | ], |
| 495 | $dashboard, |
| 496 | $commerceSetup |
| 497 | ); |
| 498 | |
| 499 | $this->container = $this->buildApplicationContainer( |
| 500 | $pdo, |
| 501 | $env['cache'], |
| 502 | $env['metadataRepo'], |
| 503 | $engineContext['engineFactory'], |
| 504 | $secContext['authController'], |
| 505 | $i18n, |
| 506 | $containerServices |
| 507 | ); |
| 508 | } |
| 509 | |
| 510 | public function getContainer(): ContainerInterface |
| 511 | { |
| 512 | return $this->container; |
| 513 | } |
| 514 | |
| 515 | public function handle(ServerRequestInterface $request): ResponseInterface |
| 516 | { |
| 517 | $fallbackHandler = new class($this) implements RequestHandlerInterface { |
| 518 | public function __construct(private ApplicationBootstrap $app) |
| 519 | { |
| 520 | } |
| 521 | |
| 522 | public function handle(ServerRequestInterface $request): ResponseInterface |
| 523 | { |
| 524 | return $this->app->dispatch($request); |
| 525 | } |
| 526 | }; |
| 527 | |
| 528 | $middlewareFactory = new \Yiisoft\Middleware\Dispatcher\MiddlewareFactory($this->container); |
| 529 | $dispatcher = (new \Yiisoft\Middleware\Dispatcher\MiddlewareDispatcher($middlewareFactory)) |
| 530 | ->withMiddlewares([ |
| 531 | fn(ServerRequestInterface $r, RequestHandlerInterface $h): ResponseInterface => ( |
| 532 | $this->securityHeadersMiddleware->process($r, $h) |
| 533 | ), |
| 534 | fn(ServerRequestInterface $r, RequestHandlerInterface $h): ResponseInterface => ( |
| 535 | $this->sessionMiddleware->process($r, $h) |
| 536 | ), |
| 537 | fn(ServerRequestInterface $r, RequestHandlerInterface $h): ResponseInterface => ( |
| 538 | $this->localeDetectionMiddleware->process($r, $h) |
| 539 | ), |
| 540 | ]); |
| 541 | |
| 542 | return $dispatcher->dispatch($request, $fallbackHandler); |
| 543 | } |
| 544 | |
| 545 | public function dispatch(ServerRequestInterface $request): ResponseInterface |
| 546 | { |
| 547 | $this->handleSessionSecurityValidation($request); |
| 548 | |
| 549 | return $this->routeDispatcher->dispatch($request); |
| 550 | } |
| 551 | |
| 552 | private function handleSessionSecurityValidation(ServerRequestInterface $request): void |
| 553 | { |
| 554 | if ($this->session->has('user_id')) { |
| 555 | $this->validateActiveSession(); |
| 556 | } elseif ($request->getAttribute('user_id') !== null) { |
| 557 | $userId = (int) $request->getAttribute('user_id'); |
| 558 | $user = $this->userRepository->findById($userId); |
| 559 | if ($user !== null && $user->isActive()) { |
| 560 | $this->currentUser->login(new Identity($userId, $user->getLocale())); |
| 561 | } |
| 562 | // @codeCoverageIgnoreStart |
| 563 | } elseif (!empty($_SESSION['user_id'])) { |
| 564 | $this->validateLegacySession(); |
| 565 | } |
| 566 | // @codeCoverageIgnoreEnd |
| 567 | } |
| 568 | |
| 569 | private function validateActiveSession(): void |
| 570 | { |
| 571 | $userId = (int) $this->session->get('user_id'); |
| 572 | $now = time(); |
| 573 | $lastActivity = (int) ($this->session->get('last_activity', $now)); |
| 574 | $idleTimeout = 900; |
| 575 | |
| 576 | if ($now - $lastActivity > $idleTimeout) { |
| 577 | $this->clearUserSession(); |
| 578 | return; |
| 579 | } |
| 580 | |
| 581 | $this->session->set('last_activity', $now); |
| 582 | $this->checkImpersonationLifetime($now); |
| 583 | |
| 584 | $isClientUser = $this->session->has('impersonated_client_user'); |
| 585 | $userRepo = ($isClientUser && $this->clientUserRepository !== null) |
| 586 | ? $this->clientUserRepository |
| 587 | : $this->userRepository; |
| 588 | |
| 589 | $user = $userRepo->findById($userId); |
| 590 | if ($user === null || !$user->isActive()) { |
| 591 | $this->clearUserSession(); |
| 592 | return; |
| 593 | } |
| 594 | |
| 595 | $this->currentUser->login(new Identity($userId, $user->getLocale())); |
| 596 | $userSessionData = [ |
| 597 | 'id' => $user->getId(), |
| 598 | 'username' => $user->getUsername(), |
| 599 | 'email' => $user->getEmail(), |
| 600 | 'is_superuser' => $user->isSuperuser(), |
| 601 | 'is_active' => true, |
| 602 | ]; |
| 603 | if ($isClientUser) { |
| 604 | $userSessionData['is_client_user'] = true; |
| 605 | } |
| 606 | $this->session->set('user', $userSessionData); |
| 607 | } |
| 608 | |
| 609 | private function checkImpersonationLifetime(int $now): void |
| 610 | { |
| 611 | if (!$this->session->has('impersonator_user')) { |
| 612 | return; |
| 613 | } |
| 614 | |
| 615 | $startedAt = (int) $this->session->get('impersonation_started_at', $now); |
| 616 | if ($now - $startedAt > 3600) { |
| 617 | $impersonator = $this->session->get('impersonator_user'); |
| 618 | if (is_array($impersonator) && !empty($impersonator['id'])) { |
| 619 | $this->session->set('user', $impersonator); |
| 620 | $this->session->set('user_id', (int) $impersonator['id']); |
| 621 | } |
| 622 | $this->session->remove('impersonator_user'); |
| 623 | $this->session->remove('impersonator_user_id'); |
| 624 | $this->session->remove('impersonated_client_user'); |
| 625 | $this->session->remove('impersonation_started_at'); |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | private function clearUserSession(): void |
| 630 | { |
| 631 | $this->session->remove('user_id'); |
| 632 | $this->session->remove('user'); |
| 633 | $this->session->remove('last_activity'); |
| 634 | $this->session->remove('impersonator_user'); |
| 635 | $this->session->remove('impersonator_user_id'); |
| 636 | $this->session->remove('impersonated_client_user'); |
| 637 | $this->session->remove('impersonation_started_at'); |
| 638 | $this->currentUser->logout(); |
| 639 | } |
| 640 | |
| 641 | // @codeCoverageIgnoreStart |
| 642 | private function validateLegacySession(): void |
| 643 | { |
| 644 | $userId = (int) $_SESSION['user_id']; |
| 645 | $user = $this->userRepository->findById($userId); |
| 646 | if ($user === null || !$user->isActive()) { |
| 647 | unset($_SESSION['user_id'], $_SESSION['user']); |
| 648 | $this->currentUser->logout(); |
| 649 | return; |
| 650 | } |
| 651 | $this->currentUser->login(new Identity($userId, $user->getLocale())); |
| 652 | $_SESSION['user'] = [ |
| 653 | 'id' => $user->getId(), |
| 654 | 'username' => $user->getUsername(), |
| 655 | 'email' => $user->getEmail(), |
| 656 | 'is_superuser' => $user->isSuperuser(), |
| 657 | 'is_active' => true, |
| 658 | ]; |
| 659 | } |
| 660 | // @codeCoverageIgnoreEnd |
| 661 | |
| 662 | public function getCentralEngineApiController(): CentralEngineApiController |
| 663 | { |
| 664 | return $this->centralEngineApiController; |
| 665 | } |
| 666 | |
| 667 | public function getPsr17Factory(): Psr17Factory |
| 668 | { |
| 669 | return $this->psr17Factory; |
| 670 | } |
| 671 | |
| 672 | public function getSession(): SessionInterface |
| 673 | { |
| 674 | return $this->session; |
| 675 | } |
| 676 | |
| 677 | /** |
| 678 | * Initializes active database connection and resolves table prefix. |
| 679 | * |
| 680 | * @param PDO|null $pdo Injected PDO connection or null. |
| 681 | * @return array{0: PDO, 1: string} Database connection and table prefix. |
| 682 | */ |
| 683 | private function initializeDatabase(?PDO $pdo): array |
| 684 | { |
| 685 | $dbConfig = InstallerConfigLoader::load(); |
| 686 | $dbParams = $dbConfig['db'] ?? []; |
| 687 | $prefix = (string)($dbParams['table_prefix'] ?? $dbParams['prefix'] ?? 'a_'); |
| 688 | |
| 689 | // @codeCoverageIgnoreStart |
| 690 | if ($pdo === null) { |
| 691 | $router = new MultiDbRouter(['default' => $dbParams]); |
| 692 | $pdo = $router->getConnection('default'); |
| 693 | } |
| 694 | // @codeCoverageIgnoreEnd |
| 695 | |
| 696 | return [$pdo, $prefix]; |
| 697 | } |
| 698 | |
| 699 | /** |
| 700 | * Initializes PSR-16 cache handler and cache wrapper. |
| 701 | * |
| 702 | * @param string $basePath Application base filesystem path. |
| 703 | * @param bool $isUnitTest Whether running in a unit test environment. |
| 704 | * @param string $appEnv Application environment name. |
| 705 | * @return array{0: Cache, 1: mixed} Cache instance and underlying handler. |
| 706 | */ |
| 707 | private function initializeCache(string $basePath, bool $isUnitTest, string $appEnv): array |
| 708 | { |
| 709 | $cacheDir = $basePath . '/storage/runtime/cache'; |
| 710 | if (!is_dir($cacheDir)) { |
| 711 | @mkdir($cacheDir, 0777, true); |
| 712 | } |
| 713 | |
| 714 | // @codeCoverageIgnoreStart |
| 715 | $cacheHandler = ($isUnitTest || $appEnv === 'test') |
| 716 | ? new ArrayCache() |
| 717 | : (new FileCache($cacheDir))->withDirectoryLevel(0); |
| 718 | // @codeCoverageIgnoreEnd |
| 719 | |
| 720 | return [new Cache($cacheHandler), $cacheHandler]; |
| 721 | } |
| 722 | |
| 723 | /** |
| 724 | * Instantiates and configures central route dispatcher. |
| 725 | * |
| 726 | * @param array<string, mixed> $context |
| 727 | */ |
| 728 | private function createRouteDispatcher( |
| 729 | SecurityAndTemplateConfigurator $sec, |
| 730 | string $basePath, |
| 731 | AuthController $authController, |
| 732 | ApiDispatcher $apiDispatcher, |
| 733 | BootstrapEngineFactory $engineFactory, |
| 734 | array $context |
| 735 | ): BootstrapRouteDispatcher { |
| 736 | $factory = new BootstrapRouteDispatcherFactory( |
| 737 | $this->psr17Factory, |
| 738 | $this->currentUser, |
| 739 | $this->session, |
| 740 | $this->userRepository |
| 741 | ); |
| 742 | |
| 743 | return $factory->create( |
| 744 | $sec, |
| 745 | $basePath, |
| 746 | $authController, |
| 747 | $apiDispatcher, |
| 748 | $engineFactory, |
| 749 | $context |
| 750 | ); |
| 751 | } |
| 752 | |
| 753 | /** |
| 754 | * Initializes core environment, database, cache, security, metadata and instance managers. |
| 755 | * |
| 756 | * @return array{ |
| 757 | * 0: PDO, |
| 758 | * 1: string, |
| 759 | * 2: SecurityAndTemplateConfigurator, |
| 760 | * 3: CacheInterface, |
| 761 | * 4: mixed, |
| 762 | * 5: MetadataRepositoryInterface, |
| 763 | * 6: InstanceContextManager, |
| 764 | * 7: RemoteInstanceEngineGateway |
| 765 | * } |
| 766 | */ |
| 767 | private function initializeCoreEnvironment(string $basePath, ?PDO $pdo): array |
| 768 | { |
| 769 | $this->loadDotEnv($basePath); |
| 770 | $this->psr17Factory = new Psr17Factory(); |
| 771 | CentralErrorHandler::register($basePath . self::LOGS_SUBPATH); |
| 772 | |
| 773 | [$pdo, $prefix] = $this->initializeDatabase($pdo); |
| 774 | $appEnv = (string)($_ENV['APP_ENV'] ?? 'dev'); |
| 775 | $appDebug = (bool)filter_var($_ENV['APP_DEBUG'] ?? true, FILTER_VALIDATE_BOOLEAN); |
| 776 | CentralErrorHandler::setDebug($appDebug); |
| 777 | |
| 778 | [$cache, $cacheHandler] = $this->initializeCache($basePath, true, $appEnv); |
| 779 | |
| 780 | $sec = new SecurityAndTemplateConfigurator( |
| 781 | $basePath, |
| 782 | $pdo, |
| 783 | $prefix, |
| 784 | $appEnv, |
| 785 | $appDebug, |
| 786 | $this->psr17Factory |
| 787 | ); |
| 788 | $this->session = $sec->session; |
| 789 | $this->sessionMiddleware = $sec->sessionMiddleware; |
| 790 | $this->securityHeadersMiddleware = $sec->securityHeadersMiddleware; |
| 791 | |
| 792 | $userPrefix = ($sec->activeProfile->name === 'client') ? 'c_' : $prefix; |
| 793 | $sqlMetadataRepo = new SqlMetadataRepository($pdo, null, null, null, $userPrefix); |
| 794 | $persistentMetadataCache = new PersistentMetadataCache($sqlMetadataRepo, $cache, 3600); |
| 795 | $metadataRepo = new RequestScopedMetadataCache($persistentMetadataCache); |
| 796 | |
| 797 | $clientInstanceRepo = new SqlClientInstanceRepository($pdo, $prefix); |
| 798 | $instanceContextManager = new InstanceContextManager($clientInstanceRepo, $this->session); |
| 799 | $remoteApiClient = new RemoteInstanceApiClient(); |
| 800 | $remoteEngineGateway = new RemoteInstanceEngineGateway($remoteApiClient); |
| 801 | |
| 802 | return [ |
| 803 | $pdo, |
| 804 | $prefix, |
| 805 | $sec, |
| 806 | $cache, |
| 807 | $cacheHandler, |
| 808 | $metadataRepo, |
| 809 | $instanceContextManager, |
| 810 | $remoteEngineGateway, |
| 811 | ]; |
| 812 | } |
| 813 | |
| 814 | /** |
| 815 | * Initializes translation, authentication, access repositories, and locale middleware. |
| 816 | * |
| 817 | * @param array{ |
| 818 | * metadataRepo: MetadataRepositoryInterface, |
| 819 | * instanceContextManager: InstanceContextManager, |
| 820 | * cache: CacheInterface |
| 821 | * } $context |
| 822 | * @return array{ |
| 823 | * 0: TranslationAndMenuConfigurator, |
| 824 | * 1: AuthController, |
| 825 | * 2: AuthApiController, |
| 826 | * 3: MfaApiController, |
| 827 | * 4: LocaleApiController, |
| 828 | * 5: CachedAccessRepository, |
| 829 | * 6: CachedProfilePermissionRepository, |
| 830 | * 7: mixed, |
| 831 | * 8: mixed |
| 832 | * } |
| 833 | */ |
| 834 | private function initializeSecurityAndTranslation( |
| 835 | string $basePath, |
| 836 | PDO $pdo, |
| 837 | string $prefix, |
| 838 | SecurityAndTemplateConfigurator $sec, |
| 839 | array $context |
| 840 | ): array { |
| 841 | $i18n = new TranslationAndMenuConfigurator( |
| 842 | $basePath, |
| 843 | $pdo, |
| 844 | $prefix, |
| 845 | $sec->twig, |
| 846 | $this->psr17Factory, |
| 847 | [ |
| 848 | 'metadataRepo' => $context['metadataRepo'], |
| 849 | 'instanceContextManager' => $context['instanceContextManager'], |
| 850 | 'appProfile' => $sec->activeProfile->name, |
| 851 | ] |
| 852 | ); |
| 853 | |
| 854 | $userAuthProvider = new UserAndAuthBootstrapProvider($this->psr17Factory, $this->session); |
| 855 | [ |
| 856 | $authApiController, |
| 857 | $mfaApiController, |
| 858 | $authController, |
| 859 | $eventDispatcher, |
| 860 | $auditRepo, |
| 861 | $this->currentUser, |
| 862 | $this->userRepository |
| 863 | ] = $userAuthProvider->initialize($pdo, $prefix, $basePath, $sec); |
| 864 | |
| 865 | $this->localeDetectionMiddleware = new LocaleDetectionMiddleware( |
| 866 | $i18n->localeContext, |
| 867 | $i18n->translator, |
| 868 | $this->session |
| 869 | ); |
| 870 | $localeApiController = new LocaleApiController( |
| 871 | $i18n->localeContext, |
| 872 | $i18n->translator, |
| 873 | $this->session, |
| 874 | $this->currentUser, |
| 875 | $this->userRepository, |
| 876 | $this->psr17Factory |
| 877 | ); |
| 878 | $accessRepo = new CachedAccessRepository(new SqlAccessRepository($pdo), $context['cache']); |
| 879 | $profileRepo = new CachedProfilePermissionRepository(new SqlProfilePermissionRepository($pdo)); |
| 880 | |
| 881 | return [ |
| 882 | $i18n, |
| 883 | $authController, |
| 884 | $authApiController, |
| 885 | $mfaApiController, |
| 886 | $localeApiController, |
| 887 | $accessRepo, |
| 888 | $profileRepo, |
| 889 | $auditRepo, |
| 890 | $eventDispatcher, |
| 891 | ]; |
| 892 | } |
| 893 | |
| 894 | /** |
| 895 | * Initializes dashboard widgets, action service, and controllers. |
| 896 | * |
| 897 | * @return array{ |
| 898 | * 0: DashboardWidgetsService, |
| 899 | * 1: DashboardCalendarActionService, |
| 900 | * 2: DashboardApiController, |
| 901 | * 3: DashboardHtmxController, |
| 902 | * 4: DashboardWebController |
| 903 | * } |
| 904 | */ |
| 905 | private function initializeDashboardSubsystem( |
| 906 | PDO $pdo, |
| 907 | string $prefix, |
| 908 | string $userPrefix, |
| 909 | SecurityAndTemplateConfigurator $sec, |
| 910 | InstanceContextManager $instanceContextManager |
| 911 | ): array { |
| 912 | $userPreferenceRepo = new SqlUserPreferenceRepository($pdo, $userPrefix); |
| 913 | $widgetsService = new DashboardWidgetsService( |
| 914 | $pdo, |
| 915 | $userPreferenceRepo, |
| 916 | $instanceContextManager, |
| 917 | $pdo, |
| 918 | $prefix, |
| 919 | $sec->activeProfile->name |
| 920 | ); |
| 921 | $actionService = new DashboardCalendarActionService( |
| 922 | $pdo, |
| 923 | $instanceContextManager, |
| 924 | $pdo, |
| 925 | $prefix |
| 926 | ); |
| 927 | $apiController = new DashboardApiController($widgetsService, $actionService, $this->psr17Factory); |
| 928 | $htmxController = new DashboardHtmxController( |
| 929 | $widgetsService, |
| 930 | $actionService, |
| 931 | $sec->twig, |
| 932 | $this->psr17Factory |
| 933 | ); |
| 934 | $webController = new DashboardWebController( |
| 935 | $widgetsService, |
| 936 | $sec->twig, |
| 937 | $this->psr17Factory, |
| 938 | $instanceContextManager |
| 939 | ); |
| 940 | |
| 941 | return [ |
| 942 | 'widgets_service' => $widgetsService, |
| 943 | 'action_service' => $actionService, |
| 944 | 'api' => $apiController, |
| 945 | 'htmx' => $htmxController, |
| 946 | 'web' => $webController, |
| 947 | ]; |
| 948 | } |
| 949 | |
| 950 | /** |
| 951 | * Initializes client user and impersonation repositories. |
| 952 | * |
| 953 | * @return array{0: SqlUserRepository, 1: SqlUserImpersonationRepository} |
| 954 | */ |
| 955 | private function initializeClientUserRepositories(PDO $pdo, SecurityAndTemplateConfigurator $sec): array |
| 956 | { |
| 957 | $this->clientUserRepository = ($sec->activeProfile->name === 'client') |
| 958 | ? $this->userRepository |
| 959 | : new SqlUserRepository($pdo, 'c_'); |
| 960 | |
| 961 | return [$this->clientUserRepository, new SqlUserImpersonationRepository($pdo, 'c_')]; |
| 962 | } |
| 963 | |
| 964 | /** |
| 965 | * Initializes mail, preferences, user profile, DAV, structure, search, and comments. |
| 966 | * |
| 967 | * @param array<string, mixed> $options |
| 968 | * @return array<string, mixed> |
| 969 | */ |
| 970 | private function initializeFeatureControllers( |
| 971 | ?PDO $pdo, |
| 972 | string $prefix, |
| 973 | SecurityAndTemplateConfigurator $sec, |
| 974 | TranslationAndMenuConfigurator $i18n, |
| 975 | InstanceContextManager $instanceContextManager, |
| 976 | array $options = [] |
| 977 | ): array { |
| 978 | $platformFeaturesProvider = new PlatformFeaturesBootstrapProvider( |
| 979 | $this->psr17Factory, |
| 980 | $this->currentUser, |
| 981 | $this->session, |
| 982 | $this->userRepository |
| 983 | ); |
| 984 | |
| 985 | $platformServicesProvider = new PlatformServicesBootstrapProvider( |
| 986 | $this->psr17Factory, |
| 987 | $this->currentUser, |
| 988 | $this->session, |
| 989 | $this->userRepository, |
| 990 | $platformFeaturesProvider |
| 991 | ); |
| 992 | |
| 993 | return $platformServicesProvider->registerAll( |
| 994 | $pdo, |
| 995 | $prefix, |
| 996 | $sec, |
| 997 | $i18n, |
| 998 | $instanceContextManager, |
| 999 | $this->centralEngineApiController->getCrudService(), |
| 1000 | $options |
| 1001 | ); |
| 1002 | } |
| 1003 | |
| 1004 | /** |
| 1005 | * Instantiates configured API Dispatcher middleware pipeline. |
| 1006 | * |
| 1007 | * @param array<string, mixed> $apis Extra API controllers. |
| 1008 | */ |
| 1009 | private function createApiDispatcher( |
| 1010 | array $apis, |
| 1011 | TranslationAndMenuConfigurator $i18n, |
| 1012 | BootstrapEngineFactory $engineFactory, |
| 1013 | SecurityAndTemplateConfigurator $sec, |
| 1014 | mixed $cacheHandler, |
| 1015 | string $prefix, |
| 1016 | PDO $pdo |
| 1017 | ): ApiDispatcher { |
| 1018 | $factory = new BootstrapApiDispatcherFactory( |
| 1019 | $this->psr17Factory, |
| 1020 | $this->currentUser, |
| 1021 | $this->session, |
| 1022 | $this->centralEngineApiController |
| 1023 | ); |
| 1024 | |
| 1025 | return $factory->create( |
| 1026 | $apis, |
| 1027 | $i18n, |
| 1028 | $engineFactory, |
| 1029 | $sec, |
| 1030 | $cacheHandler, |
| 1031 | $prefix, |
| 1032 | $pdo |
| 1033 | ); |
| 1034 | } |
| 1035 | |
| 1036 | /** |
| 1037 | * Creates and registers the central engine factory and API controller. |
| 1038 | * |
| 1039 | * @param array<string, mixed> $context |
| 1040 | * @return array{0: BootstrapEngineFactory, 1: AboutLicensesApiController, 2: AboutRequirementsApiController} |
| 1041 | */ |
| 1042 | private function initializeEngineSubsystem( |
| 1043 | PDO $pdo, |
| 1044 | string $prefix, |
| 1045 | string $basePath, |
| 1046 | SecurityAndTemplateConfigurator $sec, |
| 1047 | array $context |
| 1048 | ): array { |
| 1049 | $engineProvider = new EngineComponentsBootstrapProvider($this->psr17Factory); |
| 1050 | $result = $engineProvider->initialize($pdo, $prefix, $basePath, $sec, $context); |
| 1051 | $this->centralEngineApiController = $result[0]->centralEngineApiController; |
| 1052 | |
| 1053 | return $result; |
| 1054 | } |
| 1055 | |
| 1056 | /** |
| 1057 | * Merges core, feature, and commerce API controllers into a unified map. |
| 1058 | * |
| 1059 | * @param array<string, mixed> $coreApis |
| 1060 | * @param array<string, mixed> $featureApis |
| 1061 | * @param array<string, mixed> $commerceApis |
| 1062 | * @return array<string, mixed> |
| 1063 | */ |
| 1064 | private function assembleApis( |
| 1065 | array $coreApis, |
| 1066 | array $featureApis, |
| 1067 | array $commerceApis |
| 1068 | ): array { |
| 1069 | return array_merge($coreApis, $featureApis, $commerceApis); |
| 1070 | } |
| 1071 | |
| 1072 | /** |
| 1073 | * Instantiates user impersonation domain service and web presentation controller. |
| 1074 | * |
| 1075 | * @return array{0: UserImpersonationService, 1: ImpersonationWebController} |
| 1076 | */ |
| 1077 | private function buildImpersonationSubsystem( |
| 1078 | PDO $pdo, |
| 1079 | string $prefix, |
| 1080 | SecurityAndTemplateConfigurator $sec, |
| 1081 | ?SqlUserRepository $clientUserRepo, |
| 1082 | SqlUserImpersonationRepository $clientImpersonationRepo, |
| 1083 | InstanceContextManager $instanceContextManager |
| 1084 | ): array { |
| 1085 | $adminImpersonationRepo = new SqlUserImpersonationRepository($pdo, $prefix); |
| 1086 | $impersonationService = new UserImpersonationService( |
| 1087 | $adminImpersonationRepo, |
| 1088 | $this->userRepository, |
| 1089 | $clientImpersonationRepo, |
| 1090 | $clientUserRepo, |
| 1091 | $instanceContextManager |
| 1092 | ); |
| 1093 | $sec->twigUserExtension->setImpersonationService($impersonationService); |
| 1094 | |
| 1095 | $impersonationWebController = new ImpersonationWebController( |
| 1096 | $impersonationService, |
| 1097 | $this->userRepository, |
| 1098 | $this->session, |
| 1099 | $this->currentUser, |
| 1100 | $this->psr17Factory, |
| 1101 | $sec->twig, |
| 1102 | $clientUserRepo, |
| 1103 | $instanceContextManager, |
| 1104 | $sec->activeProfile->name, |
| 1105 | $sec->securityAuditLogger |
| 1106 | ); |
| 1107 | |
| 1108 | return [$impersonationService, $impersonationWebController]; |
| 1109 | } |
| 1110 | |
| 1111 | /** |
| 1112 | * Assembles route dispatcher options array. |
| 1113 | * |
| 1114 | * @param array<string, mixed> $featureSetup |
| 1115 | * @param array<string, mixed> $commerceSetup |
| 1116 | * @param array<string, mixed> $dashboard |
| 1117 | * @return array<string, mixed> |
| 1118 | */ |
| 1119 | private function buildRouteDispatcherOptions( |
| 1120 | MetadataRepositoryInterface $metadataRepo, |
| 1121 | InstanceContextManager $instanceContextManager, |
| 1122 | array $featureSetup, |
| 1123 | array $commerceSetup, |
| 1124 | SecurityAndTemplateConfigurator $sec, |
| 1125 | ImpersonationWebController $impersonationWebController, |
| 1126 | array $dashboard |
| 1127 | ): array { |
| 1128 | return [ |
| 1129 | 'metadata' => $metadataRepo, |
| 1130 | 'instance_context' => $instanceContextManager, |
| 1131 | 'dav_setup' => $featureSetup['dav_setup'] ?? null, |
| 1132 | 'webmail_web' => $featureSetup['webmail_web'] ?? null, |
| 1133 | 'webmail_htmx' => $featureSetup['webmail_htmx'] ?? null, |
| 1134 | 'map_web' => $featureSetup['map_web'] ?? null, |
| 1135 | 'map_htmx' => $featureSetup['map_htmx'] ?? null, |
| 1136 | 'pdf_htmx' => $featureSetup['pdf_htmx'] ?? null, |
| 1137 | 'pdf_web' => $featureSetup['pdf_web'] ?? null, |
| 1138 | 'doc_verification' => $featureSetup['doc_verification'] ?? null, |
| 1139 | 'pdf_signature_htmx' => $featureSetup['pdf_signature_htmx'] ?? null, |
| 1140 | 'pdf_queue_htmx' => $featureSetup['pdf_queue_htmx'] ?? null, |
| 1141 | 'mail_tracking_web' => $featureSetup['mail_tracking_web'] ?? null, |
| 1142 | 'mail_template_web' => $featureSetup['mail_template_web'] ?? null, |
| 1143 | 'calendar_rsvp_web' => $featureSetup['calendar_rsvp_web'] ?? null, |
| 1144 | 'access_web' => $featureSetup['access_web'] ?? null, |
| 1145 | 'comments_htmx' => $featureSetup['comments_htmx'] ?? null, |
| 1146 | 'mentions_web' => $featureSetup['mentions_web'] ?? null, |
| 1147 | 'mentions_htmx' => $featureSetup['mentions_htmx'] ?? null, |
| 1148 | 'comment_attachment_download_web' => $featureSetup['comment_attachment_download_web'] ?? null, |
| 1149 | 'impersonation_web' => $impersonationWebController, |
| 1150 | 'dashboard_web' => $dashboard['web'], |
| 1151 | 'dashboard_api' => $dashboard['api'], |
| 1152 | 'dashboard_htmx' => $dashboard['htmx'], |
| 1153 | 'tax_web' => $commerceSetup['tax_web'], |
| 1154 | 'tax_htmx' => $commerceSetup['tax_htmx'], |
| 1155 | 'discount_web' => $commerceSetup['discount_web'], |
| 1156 | 'discount_htmx' => $commerceSetup['discount_htmx'], |
| 1157 | 'inventory_web' => $commerceSetup['inventory_web'], |
| 1158 | 'inventory_htmx' => $commerceSetup['inventory_htmx'], |
| 1159 | 'inventory_items_htmx' => $commerceSetup['inventory_items_htmx'], |
| 1160 | 'mapping_web' => $commerceSetup['mapping_web'], |
| 1161 | 'mapping_htmx' => $commerceSetup['mapping_htmx'], |
| 1162 | 'quote_conversion_web' => $commerceSetup['quote_conversion_web'], |
| 1163 | 'currencies_web' => $commerceSetup['currencies_web'], |
| 1164 | 'app_profile' => $sec->activeProfile->name, |
| 1165 | ]; |
| 1166 | } |
| 1167 | |
| 1168 | /** |
| 1169 | * Builds container service definition array. |
| 1170 | * |
| 1171 | * @param array<string, mixed> $baseServices |
| 1172 | * @param array<string, mixed> $dashboard |
| 1173 | * @param array<string, mixed> $commerce |
| 1174 | * @return array<string, mixed> |
| 1175 | */ |
| 1176 | private function buildContainerServices( |
| 1177 | array $baseServices, |
| 1178 | array $dashboard, |
| 1179 | array $commerce |
| 1180 | ): array { |
| 1181 | return array_merge($baseServices, [ |
| 1182 | 'dashboard_widgets_service' => $dashboard['widgets_service'], |
| 1183 | 'dashboard_action_service' => $dashboard['action_service'], |
| 1184 | 'dashboard_htmx' => $dashboard['htmx'], |
| 1185 | 'dashboard_web' => $dashboard['web'], |
| 1186 | 'tax_web' => $commerce['tax_web'], |
| 1187 | 'tax_htmx' => $commerce['tax_htmx'], |
| 1188 | 'discount_web' => $commerce['discount_web'], |
| 1189 | 'discount_htmx' => $commerce['discount_htmx'], |
| 1190 | 'inventory_web' => $commerce['inventory_web'], |
| 1191 | 'inventory_htmx' => $commerce['inventory_htmx'], |
| 1192 | 'inventory_items_htmx' => $commerce['inventory_items_htmx'], |
| 1193 | 'mapping_web' => $commerce['mapping_web'], |
| 1194 | 'mapping_htmx' => $commerce['mapping_htmx'], |
| 1195 | 'quote_conversion_web' => $commerce['quote_conversion_web'], |
| 1196 | 'currencies_web' => $commerce['currencies_web'], |
| 1197 | 'currencies_api' => $commerce['currencies_api'], |
| 1198 | ]); |
| 1199 | } |
| 1200 | |
| 1201 | /** |
| 1202 | * Configures and creates the DI container. |
| 1203 | * |
| 1204 | * @param array<string, mixed> $services |
| 1205 | */ |
| 1206 | private function buildApplicationContainer( |
| 1207 | PDO $pdo, |
| 1208 | CacheInterface $cache, |
| 1209 | MetadataRepositoryInterface $metadataRepo, |
| 1210 | BootstrapEngineFactory $engineFactory, |
| 1211 | AuthController $authController, |
| 1212 | TranslationAndMenuConfigurator $i18n, |
| 1213 | array $services |
| 1214 | ): ContainerInterface { |
| 1215 | $coreDefinitions = $this->createCoreDefinitions( |
| 1216 | $pdo, |
| 1217 | $cache, |
| 1218 | $metadataRepo, |
| 1219 | $engineFactory, |
| 1220 | $authController, |
| 1221 | $i18n, |
| 1222 | $services |
| 1223 | ); |
| 1224 | |
| 1225 | /** @var SecurityAndTemplateConfigurator $sec */ |
| 1226 | $sec = $services['sec']; |
| 1227 | $profileDefinitions = $sec->profileManager->loadDiDefinitions($sec->activeProfile); |
| 1228 | $providers = [ |
| 1229 | \App\Core\Bootstrap\Provider\CoreServiceProvider::class, |
| 1230 | \App\Core\Bootstrap\Provider\SecurityServiceProvider::class, |
| 1231 | \App\Core\Bootstrap\Provider\EngineServiceProvider::class, |
| 1232 | \App\Core\Bootstrap\Provider\I18nServiceProvider::class, |
| 1233 | \App\Modules\User\Config\UserModuleServiceProvider::class, |
| 1234 | \App\Modules\Mail\Config\MailModuleServiceProvider::class, |
| 1235 | \App\Modules\About\Config\AboutModuleServiceProvider::class, |
| 1236 | \App\Modules\Menu\Config\MenuModuleServiceProvider::class, |
| 1237 | \App\Modules\Integrations\Config\IntegrationsServiceProvider::class, |
| 1238 | \App\Modules\Map\Config\MapModuleServiceProvider::class, |
| 1239 | ]; |
| 1240 | |
| 1241 | return ContainerConfigurator::create( |
| 1242 | array_merge($coreDefinitions, $profileDefinitions), |
| 1243 | $providers |
| 1244 | ); |
| 1245 | } |
| 1246 | |
| 1247 | /** |
| 1248 | * Constructs core DI container service definitions array. |
| 1249 | * |
| 1250 | * @param array<string, mixed> $services Extra services and API bundle. |
| 1251 | * @return array<string, mixed> Core container definitions. |
| 1252 | */ |
| 1253 | private function createCoreDefinitions( |
| 1254 | PDO $pdo, |
| 1255 | CacheInterface $cache, |
| 1256 | MetadataRepositoryInterface $metadataRepo, |
| 1257 | BootstrapEngineFactory $engineFactory, |
| 1258 | AuthController $authController, |
| 1259 | TranslationAndMenuConfigurator $i18n, |
| 1260 | array $services |
| 1261 | ): array { |
| 1262 | /** @var SecurityAndTemplateConfigurator $sec */ |
| 1263 | $sec = $services['sec']; |
| 1264 | /** @var array<string, mixed> $apis */ |
| 1265 | $apis = $services['apis']; |
| 1266 | $prefix = (string)($services['prefix'] ?? 'a_'); |
| 1267 | |
| 1268 | return [ |
| 1269 | PDO::class => $pdo, |
| 1270 | Psr17Factory::class => $this->psr17Factory, |
| 1271 | SessionInterface::class => $this->session, |
| 1272 | CurrentUser::class => $this->currentUser, |
| 1273 | CacheInterface::class => $cache, |
| 1274 | MetadataRepositoryInterface::class => $metadataRepo, |
| 1275 | UniversalCrudService::class => $this->centralEngineApiController->getCrudService(), |
| 1276 | CentralEngineApiController::class => $this->centralEngineApiController, |
| 1277 | CentralEngineWebController::class => $engineFactory->centralEngineWebController, |
| 1278 | AuthApiController::class => $apis['auth'], |
| 1279 | AuthController::class => $authController, |
| 1280 | MenuApiController::class => $i18n->menuApiController, |
| 1281 | AboutLicensesApiController::class => $apis['licenses'], |
| 1282 | AboutRequirementsApiController::class => $apis['requirements'], |
| 1283 | TranslationApiController::class => $i18n->translationApiController, |
| 1284 | SettingsApiController::class => $i18n->settingsApiController, |
| 1285 | LayoutApiController::class => $i18n->layoutApiController, |
| 1286 | CronApiController::class => $i18n->cronApiController, |
| 1287 | MailApiController::class => $apis['mail'], |
| 1288 | AuditApiController::class => $sec->auditApiController, |
| 1289 | SecurityAuditLogger::class => $sec->securityAuditLogger, |
| 1290 | AuditIntegrityService::class => $sec->auditIntegrityService, |
| 1291 | AuditRetentionService::class => $sec->auditRetentionService, |
| 1292 | ProfileManager::class => $sec->profileManager, |
| 1293 | Profile::class => $sec->activeProfile, |
| 1294 | EngineSettings::class => new EngineSettings($pdo, $prefix), |
| 1295 | AuditRepositoryInterface::class => $services['audit_repo'] ?? null, |
| 1296 | DynamicDataProviderRegistry::class => $services['data_providers'] ?? null, |
| 1297 | ApiLoggerInterface::class => new SqlApiLoggerRepository($pdo), |
| 1298 | PayloadSanitizer::class => new PayloadSanitizer(), |
| 1299 | UserImpersonationService::class => $services['impersonation_service'] ?? null, |
| 1300 | ImpersonationWebController::class => $services['impersonation_web'] ?? null, |
| 1301 | ProfilePermissionRepositoryInterface::class => $services['profile_repo'] ?? null, |
| 1302 | DashboardWidgetsService::class => $services['dashboard_widgets_service'] ?? null, |
| 1303 | DashboardCalendarActionService::class => $services['dashboard_action_service'] ?? null, |
| 1304 | DashboardApiController::class => $apis['dashboard'] ?? null, |
| 1305 | DashboardHtmxController::class => $services['dashboard_htmx'] ?? null, |
| 1306 | DashboardWebController::class => $services['dashboard_web'] ?? null, |
| 1307 | TaxSettingsWebController::class => $services['tax_web'] ?? null, |
| 1308 | TaxHtmxController::class => $services['tax_htmx'] ?? null, |
| 1309 | DiscountSettingsWebController::class => $services['discount_web'] ?? null, |
| 1310 | DiscountHtmxController::class => $services['discount_htmx'] ?? null, |
| 1311 | InventorySettingsWebController::class => $services['inventory_web'] ?? null, |
| 1312 | InventorySettingsHtmxController::class => $services['inventory_htmx'] ?? null, |
| 1313 | InventoryItemsHtmxController::class => $services['inventory_items_htmx'] ?? null, |
| 1314 | DataMappingWebController::class => $services['mapping_web'] ?? null, |
| 1315 | DataMappingHtmxController::class => $services['mapping_htmx'] ?? null, |
| 1316 | QuoteConversionWebController::class => $services['quote_conversion_web'] ?? null, |
| 1317 | CurrenciesWebController::class => $services['currencies_web'] ?? null, |
| 1318 | CurrenciesApiController::class => $services['currencies_api'] ?? null, |
| 1319 | ]; |
| 1320 | } |
| 1321 | |
| 1322 | /** |
| 1323 | * Loads environment variables from a .env file located at application root if present. |
| 1324 | * |
| 1325 | * @param string $basePath Base application filesystem path. |
| 1326 | * @return void |
| 1327 | */ |
| 1328 | private function loadDotEnv(string $basePath): void |
| 1329 | { |
| 1330 | $envPath = $basePath . '/.env'; |
| 1331 | if (!file_exists($envPath)) { |
| 1332 | return; |
| 1333 | } |
| 1334 | |
| 1335 | $lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; |
| 1336 | foreach ($lines as $line) { |
| 1337 | $trimmed = trim($line); |
| 1338 | if ($trimmed === '' || str_starts_with($trimmed, '#') || !str_contains($trimmed, '=')) { |
| 1339 | continue; |
| 1340 | } |
| 1341 | [$key, $val] = explode('=', $trimmed, 2); |
| 1342 | $key = trim($key); |
| 1343 | $val = trim($val, " \t\n\r\0\x0B\"'"); |
| 1344 | if ($key !== '' && !array_key_exists($key, $_ENV)) { |
| 1345 | $_ENV[$key] = $val; |
| 1346 | putenv("{$key}={$val}"); |
| 1347 | } |
| 1348 | } |
| 1349 | } |
| 1350 | } |
| 1351 |