config = $config; $this->languageResolver = $languageResolver; $this->marketResolver = $marketResolver; $this->storeCodeMapper = $storeCodeMapper; $this->requestPathProcessor = $requestPathProcessor; $this->storeManager = $storeManager; $this->marketCookie = $marketCookie; $this->response = $response; $this->logger = $logger; $this->localeResolver = $localeResolver; $this->scopeConfig = $scopeConfig; } /** * @param callable $proceed * @return mixed */ public function aroundDispatch( FrontControllerInterface $subject, callable $proceed, RequestInterface $request ) { if (!$this->config->isEnabled() || !$request instanceof HttpRequest) { return $proceed($request); } $pathInfo = $request->getPathInfo(); if ($this->config->shouldSkipPath($pathInfo)) { $this->resolveStoreForContextOnlyPath($request); return $proceed($request); } $realStoreCodeRedirect = $this->getRealStoreCodeRedirect($request); if ($realStoreCodeRedirect !== null) { return $realStoreCodeRedirect; } if ($this->hasInvalidMarketParam($request)) { return $proceed($request); } $marketParamRedirect = $this->getMarketParamRedirect($request); if ($marketParamRedirect !== null) { return $marketParamRedirect; } $language = $this->languageResolver->resolveFromPath($pathInfo); if ($language === null) { $this->resolveDefaultStoreForUnprefixedPath($request); return $proceed($request); } if ($this->storeCodeMapper->isDefaultLanguage($language)) { return $this->redirect( $this->buildRedirectUrl($request, $this->requestPathProcessor->stripLanguagePrefix($pathInfo)), 301 ); } $market = null; $storeCode = null; try { $market = $this->marketResolver->resolveForLanguage($request, $language); $storeCode = $this->storeCodeMapper->map($language, $market); if ($storeCode === null) { return $proceed($request); } if ($market === null) { $market = $storeCode; } $this->storeManager->setCurrentStore($storeCode); $this->applyLocaleForStore($request, $storeCode); $configLocale = (string) $this->scopeConfig->getValue( 'general/locale/code', ScopeInterface::SCOPE_STORE, $storeCode ); #$this->logger->debug('[LanguageUrlStoreResolver] locale check after set store', [ # 'path' => $request->getPathInfo(), # 'store_code' => $storeCode, # 'config_locale' => $configLocale, # 'resolver_locale' => $this->localeResolver->getLocale(), #]); // 写入当前 storeCode, 后续恢复 store context。 $this->marketCookie->set($storeCode); $request->setPathInfo($this->requestPathProcessor->stripLanguagePrefix($pathInfo)); $this->setRequestValue($request, 'language_url_prefix', $language); $this->setRequestValue($request, 'language_url_market', $market); $this->setRequestValue($request, 'language_url_store_code', $storeCode); } catch (\Throwable $exception) { $this->logger->error( '[LanguageUrlStoreResolver] Failed to resolve language URL store.', $this->buildLogContext($request, $market, $storeCode, $exception) ); } return $proceed($request); } private function getRealStoreCodeRedirect(HttpRequest $request): ?HttpResponse { $pathInfo = $request->getPathInfo(); $segments = explode('/', trim($pathInfo, '/'), 2); $storeCode = $segments[0] ?? ''; if (!$this->storeCodeMapper->isRealStoreCodeInScope($storeCode)) { return null; } $language = $this->storeCodeMapper->languageFromStoreCode($storeCode); $market = $this->storeCodeMapper->marketFromStoreCode($storeCode); if ($language === null || $market === null) { return null; } try { $this->marketCookie->set($market); $remainingPath = $segments[1] ?? ''; $targetPath = '/' . $language . ($remainingPath !== '' ? '/' . ltrim($remainingPath, '/') : ''); return $this->redirect($this->buildRedirectUrl($request, $targetPath), 301); } catch (\Throwable $exception) { $this->logger->error( '[LanguageUrlStoreResolver] Failed to redirect real store code URL.', $this->buildLogContext($request, $market, $storeCode, $exception) ); } return null; } private function getMarketParamRedirect(HttpRequest $request): ?HttpResponse { $pathInfo = $request->getPathInfo(); $market = $this->marketResolver->normalizeMarket($request->getParam('market')); if ($market === null) { return null; } $targetLanguage = $this->storeCodeMapper->languageFromStoreCode($market); if ($targetLanguage === null) { return null; } try { $this->marketCookie->set($market); $language = $this->languageResolver->resolveFromPath($pathInfo); if ($language === null) { if (!$this->isRootPath($pathInfo)) { return null; } $targetPath = $this->storeCodeMapper->isDefaultLanguage($targetLanguage) ? '/' : '/' . $targetLanguage . '/'; } else { $targetPath = $this->storeCodeMapper->isDefaultLanguage($targetLanguage) ? $this->requestPathProcessor->stripLanguagePrefix($pathInfo) : $this->requestPathProcessor->replaceLanguagePrefix($pathInfo, $targetLanguage); } return $this->redirect($this->buildRedirectUrl($request, $targetPath), 302); } catch (\Throwable $exception) { $this->logger->error( '[LanguageUrlStoreResolver] Failed to redirect market switch URL.', $this->buildLogContext($request, $market, $market, $exception) ); } return null; } private function isRootPath(string $pathInfo): bool { return trim($pathInfo, '/') === ''; } private function resolveDefaultStoreForUnprefixedPath(HttpRequest $request): void { $storeCode = null; try { $defaultStore = $this->storeManager->getWebsite()->getDefaultStore(); if (!$defaultStore || !$defaultStore->isActive()) { return; } $storeCode = (string)$defaultStore->getCode(); $this->storeManager->setCurrentStore($storeCode); $this->applyLocaleForStore($request, $storeCode); $this->marketCookie->set($storeCode); $this->setRequestValue($request, 'language_url_market', $storeCode); $this->setRequestValue($request, 'language_url_store_code', $storeCode); } catch (\Throwable $exception) { $this->logger->error( '[LanguageUrlStoreResolver] Failed to resolve the default store for the root URL.', $this->buildLogContext($request, $storeCode, $storeCode, $exception) ); } } private function hasInvalidMarketParam(HttpRequest $request): bool { $market = $request->getParam('market'); if ($market === null || $market === '') { return false; } return $this->marketResolver->normalizeMarket($market) === null; } private function resolveStoreForContextOnlyPath(HttpRequest $request): void { $storeCode = null; try { $cookieMarket = $this->marketCookie->get(); if (is_string($cookieMarket) && $cookieMarket !== '') { $normalizedMarket = $this->marketResolver->normalizeMarket($cookieMarket); if ($normalizedMarket !== null && $this->storeCodeMapper->isRealStoreCodeInScope($normalizedMarket)) { $storeCode = $normalizedMarket; } } if ($storeCode === null) { $storeCode = $this->resolveStoreCodeFromReferer($request); } if ($storeCode === null) { $defaultStore = $this->storeManager->getWebsite()->getDefaultStore(); $storeCode = $defaultStore && $defaultStore->isActive() ? (string)$defaultStore->getCode() : null; } if ($storeCode === null || !$this->storeCodeMapper->isRealStoreCodeInScope($storeCode)) { return; } $this->storeManager->setCurrentStore($storeCode); $this->applyLocaleForStore($request, $storeCode); $this->marketCookie->set($storeCode); $this->setRequestValue($request, 'language_url_market', $storeCode); $this->setRequestValue($request, 'language_url_store_code', $storeCode); $this->logger->debug('[LanguageUrlStoreResolver] context-only path store resolved', [ 'path' => $request->getPathInfo(), 'request_uri' => $request->getRequestUri(), 'store_code' => $storeCode, ]); } catch (\Throwable $exception) { $this->logger->error( '[LanguageUrlStoreResolver] Failed to resolve context-only path store.', $this->buildLogContext($request, null, $storeCode, $exception) ); } } private function resolveStoreCodeFromReferer(HttpRequest $request): ?string { $referer = (string) $request->getServer('HTTP_REFERER'); if ($referer === '') { return null; } $refererPath = (string) parse_url($referer, PHP_URL_PATH); if ($refererPath === '') { return null; } $language = $this->languageResolver->resolveFromPath($refererPath); if ($language === null) { return null; } return $this->storeCodeMapper->getDefaultStoreCodeForLanguage($language); } private function applyLocaleForStore(HttpRequest $request, string $storeCode): void { $configLocale = (string) $this->scopeConfig->getValue( 'general/locale/code', ScopeInterface::SCOPE_STORE, $storeCode ); if ($configLocale === '') { $this->logger->debug('[LanguageUrlStoreResolver] empty locale config', [ 'path' => $request->getPathInfo(), 'store_code' => $storeCode, ]); return; } $this->localeResolver->setLocale($configLocale); $this->logger->debug('[LanguageUrlStoreResolver] locale applied after set store', [ 'path' => $request->getPathInfo(), 'store_code' => $storeCode, 'config_locale' => $configLocale, 'resolver_locale' => $this->localeResolver->getLocale(), ]); } private function redirect(string $url, int $statusCode): HttpResponse { $this->response->setRedirect($url, $statusCode); return $this->response; } private function buildRedirectUrl(HttpRequest $request, string $path): string { $baseUrl = method_exists($request, 'getBaseUrl') ? rtrim((string) $request->getBaseUrl(), '/') : ''; $queryString = $this->cleanQueryString((string) parse_url($request->getRequestUri(), PHP_URL_QUERY)); $url = $baseUrl . '/' . ltrim($path, '/'); if ($queryString !== '') { $url .= '?' . $queryString; } return $url; } private function cleanQueryString(string $queryString): string { if ($queryString === '') { return ''; } parse_str($queryString, $params); unset($params['market'], $params['store'], $params['___store']); return http_build_query($params); } private function setRequestValue(HttpRequest $request, string $key, string $value): void { $request->setParam($key, $value); if (method_exists($request, 'setAlias')) { $request->setAlias($key, $value); } } /** * @return mixed[] */ private function buildLogContext( HttpRequest $request, ?string $market, ?string $storeCode, \Throwable $exception ): array { return [ 'path' => $request->getPathInfo(), 'request_uri' => $request->getRequestUri(), 'market' => $market, 'store_code' => $storeCode, 'exception' => $exception, ]; } }