Skip to content

Commit

Permalink
Show locale in url in multilingual contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
jyhein committed Apr 25, 2024
1 parent 3cb57cd commit 101a097
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 62 deletions.
29 changes: 25 additions & 4 deletions classes/core/Core.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use PKP\cache\CacheManager;
use PKP\cache\FileCache;
use PKP\config\Config;
use PKP\facades\Locale;
use SplFileInfo;

define('PKP_LIB_PATH', 'lib/pkp');
Expand Down Expand Up @@ -153,14 +154,26 @@ public static function getContextPath(string $urlInfo): string
}

/**
* Get the page present into the passed url information. It expects that urls were built using the system.
* Get localization path present into the passed
* url information.
*/
public static function getLocalization(string $urlInfo): string
{
$locale = self::_getUrlComponents($urlInfo, 0);
return Locale::isLocaleValid($locale) ? $locale : "";
}

/**
* Get the page present into
* the passed url information. It expects that urls
* were built using the system.
*
* @param $urlInfo Full url or just path info.
* @param $userVars (optional) Pass GET variables if needed (for testing only).
*/
public static function getPage(string $urlInfo, array $userVars = []): string
{
$page = static::_getUrlComponents($urlInfo, 0, 'page', $userVars);
$page = static::_getUrlComponents($urlInfo, self::_getOffset($urlInfo, 0), 'page', $userVars);
return static::cleanFileVar($page ?? '');
}

Expand All @@ -172,7 +185,7 @@ public static function getPage(string $urlInfo, array $userVars = []): string
*/
public static function getOp(string $urlInfo, array $userVars = []): string
{
$operation = static::_getUrlComponents($urlInfo, 1, 'op', $userVars);
$operation = static::_getUrlComponents($urlInfo, self::_getOffset($urlInfo, 1), 'op', $userVars);
return static::cleanFileVar($operation ?: 'index');
}

Expand All @@ -186,7 +199,7 @@ public static function getOp(string $urlInfo, array $userVars = []): string
*/
public static function getArgs(string $urlInfo, array $userVars = []): array
{
return static::_getUrlComponents($urlInfo, 2, 'path', $userVars);
return static::_getUrlComponents($urlInfo, self::_getOffset($urlInfo, 2), 'path', $userVars);
}

/**
Expand Down Expand Up @@ -452,6 +465,14 @@ private static function _getUrlComponents(string $urlInfo, int $offset, string $
return $component;
}

/**
* Get offset. Add 1 extra if localization present in URL
*/
private static function _getOffset(string $urlInfo, int $varOffset): int
{
return $varOffset + (int) !!self::getLocalization($urlInfo);
}

/**
* Extract the class name from the given file path.
*
Expand Down
6 changes: 4 additions & 2 deletions classes/core/Dispatcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ public function setUserResolver(): void
* @param array $params Optional set of name => value pairs to pass as user parameters
* @param string $anchor Optional name of anchor to add to URL
* @param bool $escape Whether or not to escape ampersands for this URL; default false.
* @param string $urlLocaleForPage Whether or not to override locale for this URL; Use '' to exclude.
*
* @return string the URL
*/
Expand All @@ -235,14 +236,15 @@ public function url(
$path = null,
$params = null,
$anchor = null,
$escape = false
$escape = false,
?string $urlLocaleForPage = null,
) {
// Instantiate the requested router
assert(isset($this->_routerNames[$shortcut]));
$routerName = $this->_routerNames[$shortcut];
$router = & $this->_instantiateRouter($routerName, $shortcut);

return $router->url($request, $newContext, $handler, $op, $path, $params, $anchor, $escape);
return $router->url($request, $newContext, $handler, $op, $path, $params, $anchor, $escape, $urlLocaleForPage);
}

//
Expand Down
108 changes: 98 additions & 10 deletions classes/core/PKPPageRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use APP\facades\Repo;
use Illuminate\Support\Facades\Auth;
use PKP\core\PKPSessionGuard;
use PKP\context\Context;
use PKP\facades\Locale;
use PKP\plugins\Hook;
use PKP\security\Role;
Expand Down Expand Up @@ -186,14 +187,16 @@ public function route($request)
$op = $this->getRequestedOp($request);

// If the application has not yet been installed we only
// allow installer pages to be displayed.
if (!Application::isInstalled()) {
if (!in_array($page, $this->getInstallationPages())) {
// A non-installation page was called although
// the system is not yet installed. Redirect to
// the installation page.
$request->redirect('index', 'install');
}
// allow installer pages to be displayed,
// or is installed and one of the installer pages was called
if (!Application::isInstalled() && !in_array($page, $this->getInstallationPages())) {
// A non-installation page was called although
// the system is not yet installed. Redirect to
// the installation page.
$request->redirect('index', 'install');
} else if (Application::isInstalled() && in_array($page, $this->getInstallationPages())) {
// Redirect to the index page
$request->redirect('index', 'index');
}

// Redirect requests from logged-out users to a context which is not
Expand Down Expand Up @@ -234,6 +237,14 @@ public function route($request)
}
}

// Set locale from URL or from 'setLocale'-op/search-params
$setLocale = ($op === 'setLocale'
? ($this->getRequestedArgs($request)[0] ?? null)
: ($page === 'install'
? ($_GET['setLocale'] ?? null)
: null));
$this->_setLocale($request, $setLocale);

// Call the selected handler's index operation if
// no operation was defined in the request.
if (empty($op)) {
Expand Down Expand Up @@ -284,6 +295,7 @@ public function route($request)
* @param null|mixed $path
* @param null|mixed $params
* @param null|mixed $anchor
* @param null|string $urlLocaleForPage
*/
public function url(
PKPRequest $request,
Expand All @@ -293,7 +305,8 @@ public function url(
$path = null,
$params = null,
$anchor = null,
$escape = false
$escape = false,
?string $urlLocaleForPage = null,
) {
//
// Base URL and Context
Expand Down Expand Up @@ -390,8 +403,14 @@ public function url(
//
// Assemble URL
//
// Context, page, operation and additional path go into the path info.
// Context, locale?, page, operation and additional path go into the path info.
$pathInfoArray = $context;
if ($urlLocaleForPage !== '') {
[$contextObject, $contextLocales] = $this->_getContextAndLocales($request, $context[0] ?? "");
if (count($contextLocales) > 1) {
$pathInfoArray[] = $this->_getLocaleForUrl($request, $contextObject, $contextLocales, $urlLocaleForPage);
}
}
if (!empty($page)) {
$pathInfoArray[] = $page;
if (!empty($op)) {
Expand Down Expand Up @@ -495,6 +514,75 @@ private function _getRequestedUrlParts($callback, $request): array|string|null
$userVars = $request->getUserVars();
return $callback($url ?? '', $userVars);
}

/**
* Get context object and context/site/all locales.
*/
private function _getContextAndLocales(PKPRequest $request, string $contextPath): array
{
return [
$context = $this->getCurrentContext() ?? (($contextPath === 'index' || !$contextPath || $contextPath === Application::CONTEXT_ID_ALL)
? null
: Application::getContextDAO()->getByPath($contextPath)),
$context?->getSupportedLocales()
?? (($contextPath === 'index')
? (Application::isInstalled()
? $request->getSite()->getSupportedLocales()
: array_keys(Locale::getLocales()))
: [])
];
}

/**
* Get locale for URL from session or primary
*/
private function _getLocaleForUrl(PKPRequest $request, ?Context $context, array $locales, ?string $urlLocaleForPage): string
{
return in_array($locale = $urlLocaleForPage ?: Locale::getLocale(), $locales)
? $locale
: (($context ?? $request->getSite())?->getPrimaryLocale() ?? Locale::getLocale());
}

/**
* Change the locale for the current user.
* Redirect to url with(out) locale if locale changed or context set to multi/monolingual.
*/
private function _setLocale(PKPRequest $request, ?string $setLocale): void
{
$contextPath = $this->_getRequestedUrlParts(['Core', 'getContextPath'], $request);
$urlLocale = $this->_getRequestedUrlParts(['Core', 'getLocalization'], $request);
$multiLingual = count($this->_getContextAndLocales($request, $contextPath)[1]) > 1;

if (!$multiLingual && !$urlLocale && !$setLocale || $multiLingual && !$setLocale && $urlLocale === Locale::getLocale()) {
return;
}

$sessionLocale = (function (string $l) use ($request): string {
$session = $request->getSession();
if (Locale::isSupported($l) && $l !== $session->get('currentLocale')) {
$session->put('currentLocale', $l);
$request->setCookieVar('currentLocale', $l);
}
// In case session current locale has been set to non-supported locale, or is null, somewhere else
if (!Locale::isSupported($session->get('currentLocale') ?? "")) {
$session->put('currentLocale', Locale::getLocale());
$request->setCookieVar('currentLocale', Locale::getLocale());
}
return $session->get('currentLocale');
})($setLocale ?? $urlLocale);

if (preg_match('#^/\w#', $source = $request->getUserVar('source') ?? "")) {
$request->redirectUrl($source);
}

$uri = Core::removeBaseUrl($setLocale ? ($_SERVER['HTTP_REFERER'] ?? "") : ($_SERVER['REQUEST_URI'] ?? ""));
$newUrlLocale = $multiLingual ? $sessionLocale . "/" : "";
$pathInfo = ($uri)
? preg_replace("#^/$contextPath" . ($urlLocale ? "/$urlLocale" : "") . "(/|$)#", "/$contextPath/$newUrlLocale", $uri, 1)
: "/index/$newUrlLocale";

$request->redirectUrl($this->getIndexUrl($request) . $pathInfo);
}
}

if (!PKP_STRICT_MODE) {
Expand Down
5 changes: 3 additions & 2 deletions classes/core/PKPRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -774,11 +774,12 @@ public function setCookieVar($key, $value, $expire = 0)
* @param mixed $path string or array containing path info for redirect.
* @param array $params Map of name => value pairs for additional parameters
* @param string $anchor Name of desired anchor on the target page
* @param string $urlLocaleForPage Whether or not to override locale for this URL; Use '' to exclude.
*/
public function redirect($context = null, $page = null, $op = null, $path = null, $params = null, $anchor = null)
public function redirect($context = null, $page = null, $op = null, $path = null, $params = null, $anchor = null, ?string $urlLocaleForPage = null)
{
$dispatcher = $this->getDispatcher();
$this->redirectUrl($dispatcher->url($this, PKPApplication::ROUTE_PAGE, $context, $page, $op, $path, $params, $anchor));
$this->redirectUrl($dispatcher->url($this, PKPApplication::ROUTE_PAGE, $context, $page, $op, $path, $params, $anchor, false, $urlLocaleForPage));
}

/**
Expand Down
8 changes: 8 additions & 0 deletions classes/core/PKPRouter.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ public function setApplication(Application $application)
$this->_application = $application;
}

/**
* get the current context
*/
public function getCurrentContext(): ?Context
{
return $this->_context;
}

/**
* get the dispatcher
*/
Expand Down
3 changes: 2 additions & 1 deletion classes/observers/events/UsageEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ protected function getRouterCanonicalUrl(Request $request, string $canonicalUrlP
null,
$canonicalUrlPage,
$canonicalUrlOp,
$canonicalUrlParams
$canonicalUrlParams,
urlLocaleForPage: ''
);

// Make sure we log the server name and not aliases.
Expand Down
18 changes: 15 additions & 3 deletions classes/template/PKPTemplateManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,18 @@ public function initialize($request)
$this->addHeader('customHeaders', $customHeaders);
}
}

if (count($supportedLocales = $currentContext?->getSupportedLocales() ?? $site->getSupportedLocales()) > 1) {
(function () use ($request, $router, $supportedLocales) {
$page = $router->getRequestedPage($request);
$op = $router->getRequestedOp($request);
$path = $router->getRequestedArgs($request);
$url = fn (string $locale = ""): string => $router->url($request, null, $page, $op, $path, urlLocaleForPage: $locale);
collect($supportedLocales)
->each(fn (string $l) => $this->addHeader("language-$l", "<link rel='alternate' hreflang='" . str_replace(['_', '@cyrillic', '@latin'], ['-', '-Cyrl', '-Latn'], $l) . "' href='" . $url($l) . "' />"));
$this->addHeader("language-xdefault", "<link rel='alternate' hreflang='x-default' href='" . $url() . "' />");
})();
}
}

if ($currentContext && !$currentContext->getEnabled()) {
Expand Down Expand Up @@ -1779,8 +1791,8 @@ public function smartyUrl($parameters, $smarty)
// Extract the reserved variables named in $paramList, and remove them
// from the parameters array. Variables remaining in parameters will be passed
// along to Request::url as extra parameters.
$params = $router = $page = $component = $anchor = $escape = $op = $path = null;
$paramList = ['params', 'router', 'context', 'page', 'component', 'op', 'path', 'anchor', 'escape'];
$params = $router = $page = $component = $anchor = $escape = $op = $path = $urlLocaleForPage = null;
$paramList = ['params', 'router', 'context', 'page', 'component', 'op', 'path', 'anchor', 'escape', 'urlLocaleForPage'];
foreach ($paramList as $parameter) {
if (isset($parameters[$parameter])) {
$$parameter = $parameters[$parameter];
Expand Down Expand Up @@ -1823,7 +1835,7 @@ public function smartyUrl($parameters, $smarty)
assert(false);
}
// Let the dispatcher create the url
return $dispatcher->url($this->_request, $router, $context, $handler, $op, $path, $parameters, $anchor, !isset($escape) || $escape);
return $dispatcher->url($this->_request, $router, $context, $handler, $op, $path, $parameters, $anchor, !isset($escape) || $escape, $urlLocaleForPage);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ Cypress.Commands.add('install', function() {
Cypress.Commands.add('login', (username, password, context) => {
context = context || 'index';
password = password || (username + username);
cy.visit('index.php/' + context + '/login/signIn', {
cy.visit('index.php/' + context + '/en/login/signIn', {
method: 'POST',
body: {username: username, password: password}
});
Expand Down
32 changes: 0 additions & 32 deletions pages/user/PKPUserHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,38 +35,6 @@ public function index($args, $request)
$request->redirect(null, null, 'profile');
}

/**
* Change the locale for the current user.
*
* @param array $args first parameter is the new locale
*/
public function setLocale($args, $request)
{
$setLocale = array_shift($args);

$site = $request->getSite();
$context = $request->getContext();
if ($context != null) {
$contextSupportedLocales = (array) $context->getSupportedLocales();
}

if (Locale::isLocaleValid($setLocale) && (!isset($contextSupportedLocales) || in_array($setLocale, $contextSupportedLocales)) && in_array($setLocale, $site->getSupportedLocales())) {
$session = $request->getSession();
$session->put('currentLocale', $setLocale);
}

$source = $request->getUserVar('source');
if (preg_match('#^/\w#', $source) === 1) {
$request->redirectUrl($source);
}

if (isset($_SERVER['HTTP_REFERER'])) {
$request->redirectUrl($_SERVER['HTTP_REFERER']);
}

$request->redirect(null, 'index');
}

/**
* Get interests for reviewer interests autocomplete.
*
Expand Down
5 changes: 3 additions & 2 deletions plugins/generic/usageEvent/PKPUsageEventPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ protected function buildUsageEvent($hookName, $args)
null,
$canonicalUrlPage,
$canonicalUrlOp,
$canonicalUrlParams
$canonicalUrlParams,
urlLocaleForPage: ''
);

// Make sure we log the server name and not aliases.
Expand Down Expand Up @@ -309,7 +310,7 @@ protected function buildUsageEvent($hookName, $args)
}

// Service URI.
$serviceUri = $router->url($request, $context->getPath());
$serviceUri = $router->url($request, $context->getPath(), urlLocaleForPage: '');

// IP and Host.
$ip = $request->getRemoteAddr();
Expand Down
Loading

0 comments on commit 101a097

Please sign in to comment.