diff --git a/Support/Jobs/BaseJob.php b/Support/Jobs/BaseJob.php index 3f2973aa069..1574a8af070 100644 --- a/Support/Jobs/BaseJob.php +++ b/Support/Jobs/BaseJob.php @@ -17,6 +17,7 @@ namespace PKP\Support\Jobs; +use APP\core\Application; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -56,7 +57,7 @@ public function __construct() protected function defaultConnection(): string { - if (defined('RUNNING_UPGRADE')) { + if (Application::isUnderMaintenance()) { return 'sync'; } diff --git a/api/v1/submissions/PKPSubmissionHandler.inc.php b/api/v1/submissions/PKPSubmissionHandler.inc.php index a23786bdeab..4fdde34d762 100644 --- a/api/v1/submissions/PKPSubmissionHandler.inc.php +++ b/api/v1/submissions/PKPSubmissionHandler.inc.php @@ -17,7 +17,6 @@ use APP\core\Application; use APP\core\Services; use APP\facades\Repo; -use APP\i18n\AppLocale; use APP\notification\Notification; use APP\notification\NotificationManager; use APP\submission\Collector; @@ -379,11 +378,6 @@ protected function getSubmissionCollector(array $queryParams): Collector */ public function get($slimRequest, $response, $args) { - AppLocale::requireComponents( - LOCALE_COMPONENT_PKP_READER, - LOCALE_COMPONENT_PKP_SUBMISSION - ); - $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $userGroupDao = DAORegistry::getDAO('UserGroupDAO'); /** @var UserGroupDAO $userGroupDao */ @@ -403,8 +397,6 @@ public function get($slimRequest, $response, $args) */ public function add($slimRequest, $response, $args) { - AppLocale::requireComponents(LOCALE_COMPONENT_APP_AUTHOR); - $request = $this->getRequest(); // Don't allow submissions to be added via the site-wide API @@ -693,7 +685,6 @@ public function addPublication($slimRequest, $response, $args) public function versionPublication($slimRequest, $response, $args) { $request = $this->getRequest(); - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_SUBMISSION, LOCALE_COMPONENT_APP_SUBMISSION); // notification.type.submissionNewVersion $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); $publication = Repo::publication()->get((int) $args['publicationId']); @@ -837,8 +828,6 @@ public function publishPublication($slimRequest, $response, $args) return $response->withStatus(403)->withJsonError('api.publication.403.alreadyPublished'); } - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_SUBMISSION, LOCALE_COMPONENT_APP_SUBMISSION); - $submissionContext = $request->getContext(); if (!$submissionContext || $submissionContext->getId() !== $submission->getData('contextId')) { $submissionContext = Services::get('context')->get($submission->getData('contextId')); diff --git a/api/v1/users/PKPUserHandler.inc.php b/api/v1/users/PKPUserHandler.inc.php index 19897f95f0c..c40ee9dc3cd 100644 --- a/api/v1/users/PKPUserHandler.inc.php +++ b/api/v1/users/PKPUserHandler.inc.php @@ -15,7 +15,7 @@ */ use APP\facades\Repo; -use APP\i18n\AppLocale; +use PKP\facades\Locale; use PKP\handler\APIHandler; use PKP\plugins\HookRegistry; use PKP\security\authorization\ContextAccessPolicy; @@ -123,7 +123,7 @@ public function getMany($slimRequest, $response, $args) ->assignedToCategoryIds(isset($params['assignedToCategory']) ? [$params['assignedToCategory']] : null) ->filterByRoleIds($params['roleIds'] ?? null) ->searchPhrase($params['searchPhrase'] ?? null) - ->orderBy($orderBy, $orderDirection, [AppLocale::getLocale(), $request->getSite()->getPrimaryLocale()]) + ->orderBy($orderBy, $orderDirection, [Locale::getLocale(), $request->getSite()->getPrimaryLocale()]) ->limit($params['count'] ?? null) ->offset($params['offset'] ?? null) ->filterByStatus($params['status'] ?? $collector::STATUS_ALL); diff --git a/api/v1/vocabs/PKPVocabHandler.inc.php b/api/v1/vocabs/PKPVocabHandler.inc.php index cbc8167ffb7..28811deca0b 100644 --- a/api/v1/vocabs/PKPVocabHandler.inc.php +++ b/api/v1/vocabs/PKPVocabHandler.inc.php @@ -15,7 +15,6 @@ */ use APP\core\Application; -use APP\i18n\AppLocale; use PKP\core\APIResponse; use PKP\core\PKPString; use PKP\db\DAORegistry; @@ -29,8 +28,8 @@ use PKP\submission\SubmissionLanguageDAO; use PKP\submission\SubmissionSubjectDAO; use Slim\Http\Request; -use Sokil\IsoCodes\IsoCodesFactory; use Stringy\Stringy; +use PKP\facades\Locale; class PKPVocabHandler extends APIHandler { @@ -77,7 +76,7 @@ public function getMany(Request $slimRequest, APIResponse $response, array $args $requestParams = $slimRequest->getQueryParams(); $vocab = $requestParams['vocab'] ?? ''; - $locale = $requestParams['locale'] ?? AppLocale::getLocale(); + $locale = $requestParams['locale'] ?? Locale::getLocale(); $term = $requestParams['term'] ?? null; if (!in_array($locale, $context->getData('supportedSubmissionLocales'))) { @@ -98,11 +97,9 @@ public function getMany(Request $slimRequest, APIResponse $response, array $args $entries = $submissionDisciplineEntryDao->getByContextId($vocab, $context->getId(), $locale, $term)->toArray(); break; case SubmissionLanguageDAO::CONTROLLED_VOCAB_SUBMISSION_LANGUAGE: - /** @var IsoCodesFactory */ - $isoCodes = app(IsoCodesFactory::class); $words = array_filter(PKPString::regexp_split('/\s+/', $term), 'strlen'); $languageNames = []; - foreach ($isoCodes->getLanguages(IsoCodesFactory::OPTIMISATION_IO) as $language) { + foreach (Locale::getLanguages() as $language) { if ($language->getAlpha2() && $language->getType() === 'L' && $language->getScope() === 'I' && Stringy::create($language->getLocalName())->containsAny($words, false)) { $languageNames[] = $language->getLocalName(); } diff --git a/classes/announcement/Repository.inc.php b/classes/announcement/Repository.inc.php index 4959965c76c..238854f6e44 100644 --- a/classes/announcement/Repository.inc.php +++ b/classes/announcement/Repository.inc.php @@ -14,7 +14,6 @@ namespace PKP\announcement; use APP\core\Request; -use APP\i18n\AppLocale; use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; use PKP\core\Core; @@ -106,11 +105,6 @@ public function getSchemaMap(): maps\Schema */ public function validate(?Announcement $object, array $props, array $allowedLocales, string $primaryLocale): array { - AppLocale::requireComponents( - LOCALE_COMPONENT_PKP_MANAGER, - LOCALE_COMPONENT_APP_MANAGER - ); - $validator = ValidatorFactory::make( $props, $this->schemaService->getValidationRules($this->dao->schema, $allowedLocales), diff --git a/classes/author/Author.inc.php b/classes/author/Author.inc.php index 705c9b83284..eb62a102456 100644 --- a/classes/author/Author.inc.php +++ b/classes/author/Author.inc.php @@ -17,7 +17,7 @@ namespace PKP\author; -use APP\i18n\AppLocale; +use PKP\facades\Locale; use PKP\db\DAORegistry; use PKP\identity\Identity; @@ -34,7 +34,7 @@ class Author extends Identity public function &getLocalizedData($key, $preferredLocale = null) { if (is_null($preferredLocale)) { - $preferredLocale = AppLocale::getLocale(); + $preferredLocale = Locale::getLocale(); } $localePrecedence = [$preferredLocale]; // the submission locale is the default locale @@ -43,8 +43,8 @@ public function &getLocalizedData($key, $preferredLocale = null) } // for settings other than givenName, familyName and affiliation (that are required) // consider also the application primary locale - if (!in_array(AppLocale::getPrimaryLocale(), $localePrecedence)) { - $localePrecedence[] = AppLocale::getPrimaryLocale(); + if (!in_array(Locale::getPrimaryLocale(), $localePrecedence)) { + $localePrecedence[] = Locale::getPrimaryLocale(); } foreach ($localePrecedence as $locale) { if (empty($locale)) { diff --git a/classes/category/Repository.inc.php b/classes/category/Repository.inc.php index 13ed8591473..dbb6aa8dffb 100644 --- a/classes/category/Repository.inc.php +++ b/classes/category/Repository.inc.php @@ -14,7 +14,6 @@ namespace PKP\category; use APP\core\Request; -use APP\i18n\AppLocale; use Illuminate\Support\Collection; use Illuminate\Support\LazyCollection; use PKP\plugins\HookRegistry; @@ -105,11 +104,6 @@ public function getSchemaMap(): maps\Schema */ public function validate(?Announcement $object, array $props, array $allowedLocales, string $primaryLocale): array { - AppLocale::requireComponents( - LOCALE_COMPONENT_PKP_MANAGER, - LOCALE_COMPONENT_APP_MANAGER - ); - $validator = ValidatorFactory::make( $props, $this->schemaService->getValidationRules($this->dao->schema, $allowedLocales), diff --git a/classes/cliTool/CommandLineTool.inc.php b/classes/cliTool/CommandLineTool.inc.php index 64bc0f97ad2..696c83563a2 100644 --- a/classes/cliTool/CommandLineTool.inc.php +++ b/classes/cliTool/CommandLineTool.inc.php @@ -25,6 +25,12 @@ use APP\core\Application; use APP\core\PageRouter; +use APP\facades\Repo; +use PKP\core\Registry; +use PKP\db\DAORegistry; +use PKP\plugins\PluginRegistry; +use PKP\security\Role; +use PKP\session\SessionManager; /** Initialization code */ define('PWD', getcwd()); @@ -32,16 +38,8 @@ if (!defined('STDIN')) { define('STDIN', fopen('php://stdin', 'r')); } -define('SESSION_DISABLE_INIT', 1); -require('./lib/pkp/includes/bootstrap.inc.php'); - -use APP\facades\Repo; -use APP\i18n\AppLocale; -use PKP\core\Registry; - -use PKP\db\DAORegistry; -use PKP\plugins\PluginRegistry; -use PKP\security\Role; +require_once './lib/pkp/includes/bootstrap.inc.php'; +SessionManager::disable(); class CommandLineTool { @@ -69,7 +67,6 @@ public function __construct($argv = []) $request->setRouter($router); // Initialize the locale and load generic plugins. - AppLocale::initialize($request); PluginRegistry::loadCategory('generic'); $this->argv = isset($argv) && is_array($argv) ? $argv : []; diff --git a/classes/cliTool/InstallTool.inc.php b/classes/cliTool/InstallTool.inc.php index 139c2a0a4e2..a0efad7e7a4 100644 --- a/classes/cliTool/InstallTool.inc.php +++ b/classes/cliTool/InstallTool.inc.php @@ -86,8 +86,6 @@ public function readParams() $this->printTitle('installer.localeSettings'); $this->readParamOptions('locale', 'locale.primary', $installForm->supportedLocales, 'en_US'); $this->readParamOptions('additionalLocales', 'installer.additionalLocales', $installForm->supportedLocales, '', true); - $this->readParamOptions('clientCharset', 'installer.clientCharset', $installForm->supportedClientCharsets, 'utf-8'); - $this->readParamOptions('connectionCharset', 'installer.connectionCharset', $installForm->supportedConnectionCharsets, ''); // File Settings $this->printTitle('installer.fileSettings'); @@ -108,7 +106,7 @@ public function readParams() // Database Settings $this->printTitle('installer.databaseSettings'); - $this->readParamOptions('databaseDriver', 'installer.databaseDriver', $installForm->checkDBDrivers()); + $this->readParamOptions('databaseDriver', 'installer.databaseDriver', $installForm->getDatabaseDriversOptions()); $this->readParam('databaseHost', 'installer.databaseHost', ''); $this->readParam('databaseUsername', 'installer.databaseUsername', ''); $this->readParam('databasePassword', 'installer.databasePassword', ''); diff --git a/classes/cliTool/UpgradeTool.inc.php b/classes/cliTool/UpgradeTool.inc.php index eb652d76cfa..ac9c9a9b06e 100644 --- a/classes/cliTool/UpgradeTool.inc.php +++ b/classes/cliTool/UpgradeTool.inc.php @@ -17,14 +17,12 @@ namespace PKP\cliTool; -define('RUNNING_UPGRADE', 1); - use APP\core\Application; - -use APP\i18n\AppLocale; use APP\install\Upgrade; use PKP\site\VersionCheck; +Application::upgrade(); + class UpgradeTool extends \PKP\cliTool\CommandLineTool { /** @var string command to execute (check|upgrade|download) */ @@ -37,7 +35,6 @@ class UpgradeTool extends \PKP\cliTool\CommandLineTool */ public function __construct($argv = []) { - Application::get()->initializeLaravelContainer(); parent::__construct($argv); if (!isset($this->argv[0]) || !in_array($this->argv[0], ['check', 'latest', 'upgrade', 'download'])) { @@ -46,7 +43,6 @@ public function __construct($argv = []) } $this->command = $this->argv[0]; - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_INSTALLER); } /** diff --git a/classes/components/PKPStatsComponent.inc.php b/classes/components/PKPStatsComponent.inc.php index 9ddd79263da..2e0f8f23967 100644 --- a/classes/components/PKPStatsComponent.inc.php +++ b/classes/components/PKPStatsComponent.inc.php @@ -42,9 +42,6 @@ class PKPStatsComponent */ public function __construct($apiUrl, $args = []) { - \AppLocale::requireComponents(LOCALE_COMPONENT_PKP_MANAGER); - \AppLocale::requireComponents(LOCALE_COMPONENT_APP_MANAGER); - $this->apiUrl = $apiUrl; $this->init($args); } diff --git a/classes/components/forms/FormComponent.inc.php b/classes/components/forms/FormComponent.inc.php index 4148c6d66cb..06f47fc7750 100644 --- a/classes/components/forms/FormComponent.inc.php +++ b/classes/components/forms/FormComponent.inc.php @@ -16,6 +16,7 @@ namespace PKP\components\forms; use Exception; +use PKP\facades\Locale; define('FIELD_POSITION_BEFORE', 'before'); define('FIELD_POSITION_AFTER', 'after'); @@ -278,9 +279,9 @@ public function getConfig() $fieldsConfig = array_map([$this, 'getFieldConfig'], $this->fields); - $visibleLocales = [\AppLocale::getLocale()]; - if (\AppLocale::getLocale() !== \AppLocale::getPrimaryLocale()) { - array_unshift($visibleLocales, \AppLocale::getPrimaryLocale()); + $visibleLocales = [Locale::getLocale()]; + if (Locale::getLocale() !== Locale::getPrimaryLocale()) { + array_unshift($visibleLocales, Locale::getPrimaryLocale()); } $config = [ @@ -290,7 +291,7 @@ public function getConfig() 'fields' => $fieldsConfig, 'groups' => $this->groups, 'pages' => $this->pages, - 'primaryLocale' => \AppLocale::getPrimaryLocale(), + 'primaryLocale' => Locale::getPrimaryLocale(), 'visibleLocales' => $visibleLocales, 'supportedFormLocales' => array_values($this->locales), // See #5690 'errors' => (object) [], diff --git a/classes/components/forms/context/PKPContextForm.inc.php b/classes/components/forms/context/PKPContextForm.inc.php index a1ca422efdd..3fff9358c34 100644 --- a/classes/components/forms/context/PKPContextForm.inc.php +++ b/classes/components/forms/context/PKPContextForm.inc.php @@ -19,7 +19,7 @@ use PKP\components\forms\FieldSelect; use PKP\components\forms\FieldText; use PKP\components\forms\FormComponent; -use Sokil\IsoCodes\IsoCodesFactory; +use PKP\facades\Locale; define('FORM_CONTEXT', 'context'); @@ -45,10 +45,8 @@ public function __construct($action, $locales, $baseUrl, $context) $this->locales = $locales; $this->method = $context ? 'PUT' : 'POST'; - $isoCodes = app(IsoCodesFactory::class); - $countries = []; - foreach ($isoCodes->getCountries() as $country) { + foreach (Locale::getCountries() as $country) { $countries[] = [ 'value' => $country->getAlpha2(), 'label' => $country->getLocalName() diff --git a/classes/components/forms/context/PKPMastheadForm.inc.php b/classes/components/forms/context/PKPMastheadForm.inc.php index 52c48e976ff..c6c9819b4d0 100644 --- a/classes/components/forms/context/PKPMastheadForm.inc.php +++ b/classes/components/forms/context/PKPMastheadForm.inc.php @@ -18,7 +18,7 @@ use PKP\components\forms\FieldSelect; use PKP\components\forms\FieldText; use PKP\components\forms\FormComponent; -use Sokil\IsoCodes\IsoCodesFactory; +use PKP\facades\Locale; define('FORM_MASTHEAD', 'masthead'); @@ -43,9 +43,8 @@ public function __construct($action, $locales, $context, $imageUploadUrl) $this->action = $action; $this->locales = $locales; - $isoCodes = app(IsoCodesFactory::class); $countries = []; - foreach ($isoCodes->getCountries() as $country) { + foreach (Locale::getCountries() as $country) { $countries[] = [ 'value' => $country->getAlpha2(), 'label' => $country->getLocalName() diff --git a/classes/components/forms/context/PKPPaymentSettingsForm.inc.php b/classes/components/forms/context/PKPPaymentSettingsForm.inc.php index 1b169bf9f8d..2ef6f01d387 100644 --- a/classes/components/forms/context/PKPPaymentSettingsForm.inc.php +++ b/classes/components/forms/context/PKPPaymentSettingsForm.inc.php @@ -17,8 +17,8 @@ use PKP\components\forms\FieldOptions; use PKP\components\forms\FieldSelect; use PKP\components\forms\FormComponent; - -use Sokil\IsoCodes\IsoCodesFactory; +use PKP\facades\Locale; +use PKP\plugins\PluginRegistry; define('FORM_PAYMENT_SETTINGS', 'paymentSettings'); @@ -43,8 +43,7 @@ public function __construct($action, $locales, $context) $this->locales = $locales; $currencies = []; - $isoCodes = app(IsoCodesFactory::class); - foreach ($isoCodes->getCurrencies() as $currency) { + foreach (Locale::getCurrencies() as $currency) { $currencies[] = [ 'value' => $currency->getLetterCode(), 'label' => $currency->getLocalName(), @@ -52,7 +51,7 @@ public function __construct($action, $locales, $context) } // Ensure payment method plugins can hook in - $paymentPlugins = \PluginRegistry::loadCategory('paymethod', true); + $paymentPlugins = PluginRegistry::loadCategory('paymethod', true); $pluginList = []; foreach ($paymentPlugins as $plugin) { $pluginList[] = [ diff --git a/classes/components/listPanels/PKPAnnouncementsListPanel.inc.php b/classes/components/listPanels/PKPAnnouncementsListPanel.inc.php index b2b81088176..a7b32464f30 100644 --- a/classes/components/listPanels/PKPAnnouncementsListPanel.inc.php +++ b/classes/components/listPanels/PKPAnnouncementsListPanel.inc.php @@ -36,34 +36,24 @@ class PKPAnnouncementsListPanel extends ListPanel */ public function getConfig() { - \AppLocale::requireComponents(LOCALE_COMPONENT_PKP_MANAGER); - \AppLocale::requireComponents(LOCALE_COMPONENT_APP_MANAGER); $request = \Application::get()->getRequest(); - - $config = parent::getConfig(); - - $config = array_merge( - $config, - [ - 'addAnnouncementLabel' => __('grid.action.addAnnouncement'), - 'apiUrl' => $this->apiUrl, - 'confirmDeleteMessage' => __('manager.announcements.confirmDelete'), - 'count' => $this->count, - 'deleteAnnouncementLabel' => __('manager.announcements.deleteAnnouncement'), - 'editAnnouncementLabel' => __('manager.announcements.edit'), - 'form' => $this->form->getConfig(), - 'itemsMax' => $this->itemsMax, - 'urlBase' => $request->getDispatcher()->url( - $request, - \PKPApplication::ROUTE_PAGE, - $request->getContext()->getPath(), - 'announcement', - 'view', - '__id__' - ) - ] - ); - - return $config; + return parent::getConfig() + [ + 'addAnnouncementLabel' => __('grid.action.addAnnouncement'), + 'apiUrl' => $this->apiUrl, + 'confirmDeleteMessage' => __('manager.announcements.confirmDelete'), + 'count' => $this->count, + 'deleteAnnouncementLabel' => __('manager.announcements.deleteAnnouncement'), + 'editAnnouncementLabel' => __('manager.announcements.edit'), + 'form' => $this->form->getConfig(), + 'itemsMax' => $this->itemsMax, + 'urlBase' => $request->getDispatcher()->url( + $request, + \PKPApplication::ROUTE_PAGE, + $request->getContext()->getPath(), + 'announcement', + 'view', + '__id__' + ) + ]; } } diff --git a/classes/components/listPanels/PKPContributorsListPanel.inc.php b/classes/components/listPanels/PKPContributorsListPanel.inc.php index e78fc8c36d2..b889b6c01eb 100644 --- a/classes/components/listPanels/PKPContributorsListPanel.inc.php +++ b/classes/components/listPanels/PKPContributorsListPanel.inc.php @@ -14,8 +14,6 @@ namespace PKP\components\listPanels; -use APP\i18n\AppLocale; - class PKPContributorsListPanel extends ListPanel { /** @param \PKP\components\forms\publication\PKPContributorForm Form for adding or editing a contributor */ @@ -32,9 +30,6 @@ class PKPContributorsListPanel extends ListPanel */ public function getConfig() { - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_MANAGER); - AppLocale::requireComponents(LOCALE_COMPONENT_APP_MANAGER); - $config = parent::getConfig(); // Remove some props not used in this list panel diff --git a/classes/components/listPanels/PKPDoiListPanel.inc.php b/classes/components/listPanels/PKPDoiListPanel.inc.php index c681e24e522..a4d83c8999b 100644 --- a/classes/components/listPanels/PKPDoiListPanel.inc.php +++ b/classes/components/listPanels/PKPDoiListPanel.inc.php @@ -16,7 +16,6 @@ namespace PKP\components\listPanels; use APP\core\Application; -use APP\i18n\AppLocale; use APP\template\TemplateManager; use PKP\doi\Doi; use PKP\plugins\HookRegistry; @@ -61,14 +60,6 @@ abstract class PKPDoiListPanel extends ListPanel */ public function getConfig() { - AppLocale::requireComponents( - [ - LOCALE_COMPONENT_PKP_SUBMISSION, - LOCALE_COMPONENT_PKP_MANAGER, - LOCALE_COMPONENT_APP_MANAGER - ] - ); - $config = parent::getConfig(); $config['apiUrl'] = $this->apiUrl; $config['doiApiUrl'] = $this->doiApiUrl; diff --git a/classes/components/listPanels/PKPEmailTemplatesListPanel.inc.php b/classes/components/listPanels/PKPEmailTemplatesListPanel.inc.php index 7023343ebc5..c47ae56a745 100644 --- a/classes/components/listPanels/PKPEmailTemplatesListPanel.inc.php +++ b/classes/components/listPanels/PKPEmailTemplatesListPanel.inc.php @@ -54,10 +54,6 @@ public function __construct($id, $title, $supportedLocales, $args = []) */ public function getConfig() { - \AppLocale::requireComponents(LOCALE_COMPONENT_PKP_MANAGER); - \AppLocale::requireComponents(LOCALE_COMPONENT_APP_MANAGER); - \AppLocale::requireComponents(LOCALE_COMPONENT_APP_DEFAULT); - $config = parent::getConfig(); $config['apiUrl'] = $this->apiUrl; diff --git a/classes/components/listPanels/PKPSubmissionsListPanel.inc.php b/classes/components/listPanels/PKPSubmissionsListPanel.inc.php index b291fb0aa28..b6229df2fdf 100644 --- a/classes/components/listPanels/PKPSubmissionsListPanel.inc.php +++ b/classes/components/listPanels/PKPSubmissionsListPanel.inc.php @@ -16,7 +16,6 @@ use APP\core\Application; use APP\facades\Repo; -use APP\i18n\AppLocale; use APP\template\TemplateManager; use PKP\components\forms\FieldAutosuggestPreset; use PKP\components\forms\FieldSelectUsers; @@ -57,7 +56,6 @@ abstract class PKPSubmissionsListPanel extends ListPanel */ public function getConfig() { - AppLocale::requireComponents([LOCALE_COMPONENT_PKP_SUBMISSION, LOCALE_COMPONENT_APP_SUBMISSION, LOCALE_COMPONENT_PKP_EDITOR, LOCALE_COMPONENT_APP_EDITOR]); $request = Application::get()->getRequest(); $context = $request->getContext(); diff --git a/classes/context/Context.inc.php b/classes/context/Context.inc.php index 8f1157d20f3..f92dac453ce 100644 --- a/classes/context/Context.inc.php +++ b/classes/context/Context.inc.php @@ -16,10 +16,10 @@ namespace PKP\context; use APP\core\Application; -use APP\i18n\AppLocale; - -use APP\plugins\IDoiRegistrationAgency; +use Illuminate\Support\Arr; use PKP\config\Config; +use PKP\facades\Locale; +use APP\plugins\IDoiRegistrationAgency; use PKP\plugins\Plugin; use PKP\plugins\PluginRegistry; use PKP\statistics\PKPStatisticsHelper; @@ -304,13 +304,9 @@ public function getAcronym($locale) */ public function getLocalizedFavicon() { - $faviconArray = $this->getData('favicon'); - foreach ([AppLocale::getLocale(), AppLocale::getPrimaryLocale()] as $locale) { - if (isset($faviconArray[$locale])) { - return $faviconArray[$locale]; - } - } - return null; + $favicons = $this->getData('favicon'); + $locale = Arr::first([Locale::getLocale(), Locale::getPrimaryLocale()], fn (string $locale) => isset($favicons[$locale])); + return $favicons[$locale] ?? null; } /** @@ -331,23 +327,10 @@ public function getSupportedFormLocales() */ public function getSupportedFormLocaleNames() { - $supportedLocales = & $this->getData('supportedFormLocaleNames'); - - if (!isset($supportedLocales)) { - $supportedLocales = []; - $localeNames = & AppLocale::getAllLocales(); - - $locales = $this->getSupportedFormLocales(); - if (!isset($locales) || !is_array($locales)) { - $locales = []; - } - - foreach ($locales as $localeKey) { - $supportedLocales[$localeKey] = $localeNames[$localeKey]; - } - } - - return $supportedLocales; + return $this->getData('supportedFormLocaleNames') ?? array_map( + fn (string $locale) => Locale::getMetadata($locale)->getDisplayName(), + array_combine($locales = $this->getSupportedFormLocales(), $locales) + ); } /** @@ -369,23 +352,10 @@ public function getSupportedSubmissionLocales() */ public function getSupportedSubmissionLocaleNames() { - $supportedLocales = & $this->getData('supportedSubmissionLocaleNames'); - - if (!isset($supportedLocales)) { - $supportedLocales = []; - $localeNames = & AppLocale::getAllLocales(); - - $locales = $this->getSupportedSubmissionLocales(); - if (!isset($locales) || !is_array($locales)) { - $locales = []; - } - - foreach ($locales as $localeKey) { - $supportedLocales[$localeKey] = $localeNames[$localeKey]; - } - } - - return $supportedLocales; + return $this->getData('supportedSubmissionLocaleNames') ?? array_map( + fn (string $locale) => Locale::getMetadata($locale)->getDisplayName(), + array_combine($locales = $this->getSupportedSubmissionLocales(), $locales) + ); } /** @@ -406,23 +376,10 @@ public function getSupportedLocales() */ public function getSupportedLocaleNames() { - $supportedLocales = & $this->getData('supportedLocaleNames'); - - if (!isset($supportedLocales)) { - $supportedLocales = []; - $localeNames = & AppLocale::getAllLocales(); - - $locales = $this->getSupportedLocales(); - if (!isset($locales) || !is_array($locales)) { - $locales = []; - } - - foreach ($locales as $localeKey) { - $supportedLocales[$localeKey] = $localeNames[$localeKey]; - } - } - - return $supportedLocales; + return $this->getData('supportedLocaleNames') ?? array_map( + fn (string $locale) => Locale::getMetadata($locale)->getDisplayName(), + array_combine($locales = $this->getSupportedLocales(), $locales) + ); } /** @@ -454,15 +411,7 @@ public function getDateTimeFormats($format) */ public function getLocalizedDateFormatShort($locale = null) { - if (is_null($locale)) { - $locale = AppLocale::getLocale(); - } - $localizedData = $this->getData('dateFormatShort', $locale); - if (empty($localizedData)) { - $localizedData = Config::getVar('general', 'date_format_short'); - } - - return $localizedData; + return $this->getData('dateFormatShort', $locale ?? Locale::getLocale()) ?: Config::getVar('general', 'date_format_short'); } /** @@ -474,15 +423,7 @@ public function getLocalizedDateFormatShort($locale = null) */ public function getLocalizedDateFormatLong($locale = null) { - if (is_null($locale)) { - $locale = AppLocale::getLocale(); - } - $localizedData = $this->getData('dateFormatLong', $locale); - if (empty($localizedData)) { - $localizedData = Config::getVar('general', 'date_format_long'); - } - - return $localizedData; + return $this->getData('dateFormatLong', $locale ?? Locale::getLocale()) ?: Config::getVar('general', 'date_format_long'); } /** @@ -494,15 +435,7 @@ public function getLocalizedDateFormatLong($locale = null) */ public function getLocalizedTimeFormat($locale = null) { - if (is_null($locale)) { - $locale = AppLocale::getLocale(); - } - $localizedData = $this->getData('timeFormat', $locale); - if (empty($localizedData)) { - $localizedData = Config::getVar('general', 'time_format'); - } - - return $localizedData; + return $this->getData('timeFormat', $locale ?? Locale::getLocale()) ?: Config::getVar('general', 'time_format'); } /** @@ -514,15 +447,7 @@ public function getLocalizedTimeFormat($locale = null) */ public function getLocalizedDateTimeFormatShort($locale = null) { - if (is_null($locale)) { - $locale = AppLocale::getLocale(); - } - $localizedData = $this->getData('datetimeFormatShort', $locale); - if (empty($localizedData)) { - $localizedData = Config::getVar('general', 'datetime_format_short'); - } - - return $localizedData; + return $this->getData('datetimeFormatShort', $locale ?? Locale::getLocale()) ?: Config::getVar('general', 'datetime_format_short'); } /** @@ -534,15 +459,7 @@ public function getLocalizedDateTimeFormatShort($locale = null) */ public function getLocalizedDateTimeFormatLong($locale = null) { - if (is_null($locale)) { - $locale = AppLocale::getLocale(); - } - $localizedData = $this->getData('datetimeFormatLong', $locale); - if (empty($localizedData)) { - $localizedData = Config::getVar('general', 'datetime_format_long'); - } - - return $localizedData; + return $this->getData('datetimeFormatLong', $locale ?? Locale::getLocale()) ?: Config::getVar('general', 'datetime_format_long'); } /** diff --git a/classes/controlledVocab/ControlledVocabDAO.inc.php b/classes/controlledVocab/ControlledVocabDAO.inc.php index fefc94bf1be..c60e91cc672 100644 --- a/classes/controlledVocab/ControlledVocabDAO.inc.php +++ b/classes/controlledVocab/ControlledVocabDAO.inc.php @@ -17,7 +17,7 @@ namespace PKP\controlledVocab; -use APP\i18n\AppLocale; +use PKP\facades\Locale; use PKP\db\DAORegistry; class ControlledVocabDAO extends \PKP\db\DAO @@ -250,8 +250,8 @@ public function enumerate($controlledVocabId, $settingName = 'name') WHERE e.controlled_vocab_id = ? ORDER BY e.seq', [ - $settingName, AppLocale::getLocale(), // Current locale - $settingName, AppLocale::getPrimaryLocale(), // Primary locale + $settingName, Locale::getLocale(), // Current locale + $settingName, Locale::getPrimaryLocale(), // Primary locale $settingName, '', // No locale (int) $controlledVocabId ] diff --git a/classes/controllers/grid/DataObjectGridCellProvider.inc.php b/classes/controllers/grid/DataObjectGridCellProvider.inc.php index f8443bf40cd..3b38accaf06 100644 --- a/classes/controllers/grid/DataObjectGridCellProvider.inc.php +++ b/classes/controllers/grid/DataObjectGridCellProvider.inc.php @@ -19,7 +19,7 @@ namespace PKP\controllers\grid; -use APP\i18n\AppLocale; +use PKP\facades\Locale; class DataObjectGridCellProvider extends GridCellProvider { @@ -48,7 +48,7 @@ public function setLocale($locale) public function getLocale() { if (empty($this->_locale)) { - return AppLocale::getLocale(); + return Locale::getLocale(); } return $this->_locale; } diff --git a/classes/controllers/grid/GridCellProvider.inc.php b/classes/controllers/grid/GridCellProvider.inc.php index ab1b8157acf..9c3fea3758b 100644 --- a/classes/controllers/grid/GridCellProvider.inc.php +++ b/classes/controllers/grid/GridCellProvider.inc.php @@ -18,7 +18,7 @@ namespace PKP\controllers\grid; -use APP\i18n\AppLocale; +use PKP\facades\Locale; use APP\template\TemplateManager; class GridCellProvider @@ -63,7 +63,7 @@ public function render($request, $row, $column) 'column' => $column, 'actions' => $this->getCellActions($request, $row, $column), 'flags' => $column->getFlags(), - 'formLocales' => AppLocale::getSupportedFormLocales(), + 'formLocales' => Locale::getSupportedFormLocales(), ]); $template = $column->getTemplate(); assert(!empty($template)); diff --git a/classes/controllers/grid/GridHandler.inc.php b/classes/controllers/grid/GridHandler.inc.php index eb3876ced75..f9dfb89c5cb 100644 --- a/classes/controllers/grid/GridHandler.inc.php +++ b/classes/controllers/grid/GridHandler.inc.php @@ -38,7 +38,6 @@ namespace PKP\controllers\grid; -use APP\i18n\AppLocale; use APP\template\TemplateManager; use Illuminate\Support\Enumerable; use Illuminate\Support\LazyCollection; @@ -671,9 +670,6 @@ public function initialize($request, $args = null) { parent::initialize($request); - // Load grid-specific translations - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_GRID, LOCALE_COMPONENT_APP_COMMON); - if ($this->getFilterForm() && $this->isFilterFormCollapsible()) { $this->addAction( new LinkAction( diff --git a/classes/controllers/grid/plugins/PluginGridHandler.inc.php b/classes/controllers/grid/plugins/PluginGridHandler.inc.php index 83eebf2cb06..c572f3e7542 100644 --- a/classes/controllers/grid/plugins/PluginGridHandler.inc.php +++ b/classes/controllers/grid/plugins/PluginGridHandler.inc.php @@ -15,7 +15,6 @@ namespace PKP\controllers\grid\plugins; -use APP\i18n\AppLocale; use APP\notification\NotificationManager; use PKP\controllers\grid\CategoryGridHandler; use PKP\controllers\grid\GridColumn; @@ -79,9 +78,6 @@ public function initialize($request, $args = null) { parent::initialize($request, $args); - // Load language components - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_MANAGER, LOCALE_COMPONENT_PKP_COMMON, LOCALE_COMPONENT_APP_MANAGER); - // Basic grid configuration $this->setTitle('common.plugins'); diff --git a/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.inc.php b/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.inc.php index e0f8018e9fb..0ebb3590fa3 100644 --- a/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.inc.php +++ b/classes/controllers/grid/users/reviewer/PKPReviewerGridHandler.inc.php @@ -17,7 +17,6 @@ use APP\core\Application; use APP\facades\Repo; -use APP\i18n\AppLocale; use APP\log\SubmissionEventLogEntry; use APP\notification\NotificationManager; use APP\template\TemplateManager; @@ -213,17 +212,6 @@ public function getReviewRound() public function initialize($request, $args = null) { parent::initialize($request, $args); - - // Load submission-specific translations - AppLocale::requireComponents( - LOCALE_COMPONENT_PKP_SUBMISSION, - LOCALE_COMPONENT_PKP_MANAGER, - LOCALE_COMPONENT_PKP_USER, - LOCALE_COMPONENT_PKP_EDITOR, - LOCALE_COMPONENT_PKP_REVIEWER, - LOCALE_COMPONENT_APP_EDITOR - ); - $this->setTitle('user.role.reviewers'); // Grid actions diff --git a/classes/controllers/listbuilder/MultilingualListbuilderGridColumn.inc.php b/classes/controllers/listbuilder/MultilingualListbuilderGridColumn.inc.php index 46cc9f809eb..ff97f955d6f 100644 --- a/classes/controllers/listbuilder/MultilingualListbuilderGridColumn.inc.php +++ b/classes/controllers/listbuilder/MultilingualListbuilderGridColumn.inc.php @@ -15,7 +15,7 @@ namespace PKP\controllers\listbuilder; -use APP\i18n\AppLocale; +use PKP\facades\Locale; class MultilingualListbuilderGridColumn extends ListbuilderGridColumn { @@ -44,7 +44,7 @@ public function __construct( // Provide a default set of available locales if not specified if (!$availableLocales) { - $availableLocales = AppLocale::getSupportedFormLocales(); + $availableLocales = Locale::getSupportedFormLocales(); } // Set some flags for multilingual support diff --git a/classes/controllers/modals/editorDecision/PKPEditorDecisionHandler.inc.php b/classes/controllers/modals/editorDecision/PKPEditorDecisionHandler.inc.php index 3d8ea7442b4..7687a5dfbd5 100644 --- a/classes/controllers/modals/editorDecision/PKPEditorDecisionHandler.inc.php +++ b/classes/controllers/modals/editorDecision/PKPEditorDecisionHandler.inc.php @@ -16,7 +16,6 @@ namespace PKP\controllers\modals\editorDecision; use APP\handler\Handler; -use APP\i18n\AppLocale; use APP\notification\NotificationManager; use APP\workflow\EditorDecisionActionsManager; use PKP\core\JSONMessage; @@ -68,20 +67,6 @@ public function authorize($request, &$args, $roleAssignments) return true; } - /** - * @copydoc PKPHandler::initialize() - */ - public function initialize($request) - { - AppLocale::requireComponents( - LOCALE_COMPONENT_APP_COMMON, - LOCALE_COMPONENT_APP_EDITOR, - LOCALE_COMPONENT_APP_SUBMISSION, - LOCALE_COMPONENT_PKP_EDITOR, - LOCALE_COMPONENT_PKP_SUBMISSION - ); - } - // // Public handler actions @@ -293,7 +278,6 @@ public function importPeerReviews($args, $request) $reviewAssignments = $reviewAssignmentDao->getBySubmissionId($submission->getId(), $reviewRound->getId()); $reviewIndexes = $reviewAssignmentDao->getReviewIndexesForRound($submission->getId(), $reviewRound->getId()); - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_SUBMISSION); $body = ''; $textSeparator = '------------------------------------------------------'; diff --git a/classes/controllers/tab/workflow/PKPReviewRoundTabHandler.inc.php b/classes/controllers/tab/workflow/PKPReviewRoundTabHandler.inc.php index d4fdcb7f6b9..00c0e69c87e 100644 --- a/classes/controllers/tab/workflow/PKPReviewRoundTabHandler.inc.php +++ b/classes/controllers/tab/workflow/PKPReviewRoundTabHandler.inc.php @@ -16,7 +16,6 @@ namespace PKP\controllers\tab\workflow; use APP\handler\Handler; -use APP\i18n\AppLocale; use APP\notification\Notification; use APP\template\TemplateManager; use PKP\core\JSONMessage; @@ -61,7 +60,6 @@ public function externalReviewRound($args, $request) */ public function setupTemplate($request) { - AppLocale::requireComponents(LOCALE_COMPONENT_APP_EDITOR); parent::setupTemplate($request); } diff --git a/classes/core/APIResponse.inc.php b/classes/core/APIResponse.inc.php index 7ea3731cf8c..45e39b6f727 100644 --- a/classes/core/APIResponse.inc.php +++ b/classes/core/APIResponse.inc.php @@ -31,7 +31,7 @@ public function withJsonError($msg, $params = null) return $this->withJson( [ 'error' => $msg, - 'errorMessage' => __($msg, $params), + 'errorMessage' => __($msg, $params ?? []), ] ); } diff --git a/classes/core/APIRouter.inc.php b/classes/core/APIRouter.inc.php index 40f425ecf6d..115b75ef2fb 100644 --- a/classes/core/APIRouter.inc.php +++ b/classes/core/APIRouter.inc.php @@ -19,7 +19,6 @@ namespace PKP\core; -use APP\i18n\AppLocale; use Exception; use PKP\config\Config; @@ -109,7 +108,6 @@ public function route($request) $sourceFile = sprintf('api/%s/%s/index.php', $this->getVersion(), $this->getEntity()); if (!file_exists($sourceFile)) { - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_API, LOCALE_COMPONENT_APP_API); http_response_code('404'); header('Content-Type: application/json'); echo json_encode([ @@ -119,7 +117,7 @@ public function route($request) exit; } - if (!defined('SESSION_DISABLE_INIT')) { + if (!SessionManager::isDisabled()) { // Initialize session SessionManager::getManager(); } @@ -161,7 +159,6 @@ public function handleAuthorizationFailure( $authorizationMessage, array $messageParams = [] ) { - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_API, LOCALE_COMPONENT_APP_API); http_response_code('403'); header('Content-Type: application/json'); echo json_encode([ diff --git a/classes/core/Core.inc.php b/classes/core/Core.inc.php index 7ca97f33b8a..d2c5b383ecc 100644 --- a/classes/core/Core.inc.php +++ b/classes/core/Core.inc.php @@ -43,13 +43,7 @@ class Core public static function getBaseDir() { static $baseDir; - - if (!isset($baseDir)) { - // Need to change if the index file moves - $baseDir = dirname(INDEX_FILE_LOCATION); - } - - return $baseDir; + return $baseDir ??= dirname(INDEX_FILE_LOCATION); } /** diff --git a/classes/core/DataObject.inc.php b/classes/core/DataObject.inc.php index aa081a3d987..b76f2fcc683 100644 --- a/classes/core/DataObject.inc.php +++ b/classes/core/DataObject.inc.php @@ -17,7 +17,8 @@ namespace PKP\core; -use APP\i18n\AppLocale; +use APP\core\Application; +use PKP\facades\Locale; class DataObject { @@ -59,7 +60,7 @@ public function __construct() */ public function getLocalizedData($key, $preferredLocale = null) { - $localePrecedence = AppLocale::getLocalePrecedence(); + $localePrecedence = $this->_getLocalePrecedence(); foreach ($localePrecedence as $locale) { $value = & $this->getData($key, $locale); if (!empty($value)) { @@ -69,17 +70,39 @@ public function getLocalizedData($key, $preferredLocale = null) } // Fallback: Get the first available piece of data. - $data = & $this->getData($key, null); + $data = $this->getData($key, null); if (!empty($data)) { $locales = array_keys($data); $firstLocale = array_shift($locales); return $data[$firstLocale]; } - // No data available; return null. - unset($data); - $data = null; - return $data; + return null; + } + + /** + * Get the stack of "important" locales, most important first. + + * @return string[] + */ + private function _getLocalePrecedence(): array + { + static $localePrecedence; + if (!isset($localePrecedence)) { + $request = Application::get()->getRequest(); + $localePrecedence = [Locale::getLocale()]; + + $context = $request->getContext(); + if ($context && !in_array($context->getPrimaryLocale(), $localePrecedence)) { + $localePrecedence[] = $context->getPrimaryLocale(); + } + + $site = $request->getSite(); + if ($site && !in_array($site->getPrimaryLocale(), $localePrecedence)) { + $localePrecedence[] = $site->getPrimaryLocale(); + } + } + return $localePrecedence; } /** diff --git a/classes/core/Dispatcher.inc.php b/classes/core/Dispatcher.inc.php index 433887ac1ef..0e139c92c45 100644 --- a/classes/core/Dispatcher.inc.php +++ b/classes/core/Dispatcher.inc.php @@ -16,7 +16,6 @@ namespace PKP\core; use APP\core\Services; -use APP\i18n\AppLocale; use PKP\config\Config; use PKP\plugins\HookRegistry; @@ -134,8 +133,6 @@ public function dispatch($request) fatalError('None of the configured routers supports this request.'); } - AppLocale::initialize($request); - // Can we serve a cached response? if ($router->isCacheable($request)) { $this->_requestCallbackHack = & $request; @@ -259,7 +256,7 @@ public function _displayCached($router, $request) return false; } - header('Content-Type: text/html; charset=' . Config::getVar('i18n', 'client_charset')); + header('Content-Type: text/html; charset=utf-8'); echo $contents; return true; diff --git a/classes/core/ExportableTrait.inc.php b/classes/core/ExportableTrait.inc.php new file mode 100644 index 00000000000..363cbeb05ee --- /dev/null +++ b/classes/core/ExportableTrait.inc.php @@ -0,0 +1,31 @@ + $value) { + $object->$key = $value; + } + return $object; + } +} diff --git a/classes/core/PKPApplication.inc.php b/classes/core/PKPApplication.inc.php index e24a86e0aff..428deccd0ee 100644 --- a/classes/core/PKPApplication.inc.php +++ b/classes/core/PKPApplication.inc.php @@ -19,15 +19,20 @@ use APP\core\Application; use APP\core\Request; -use APP\i18n\AppLocale; +use PKP\facades\Locale; use APP\statistics\StatisticsHelper; +use DateTime; +use DateTimeZone; use Exception; +use GuzzleHttp\Client; +use Illuminate\Database\Events\QueryExecuted; +use Illuminate\Database\MySqlConnection; +use Illuminate\Support\Facades\DB; use PKP\config\Config; - use PKP\db\DAORegistry; -use PKP\i18n\PKPLocale; use PKP\plugins\PluginRegistry; use PKP\security\Role; +use PKP\session\SessionManager; use PKP\statistics\PKPStatisticsHelper; interface iPKPApplicationInfoProvider @@ -193,29 +198,26 @@ class_alias('\PKP\core\PKPApplication', '\PKPApplication'); } ini_set('display_errors', Config::getVar('debug', 'display_errors', ini_get('display_errors'))); - if (!defined('SESSION_DISABLE_INIT') && !Config::getVar('general', 'installed')) { - define('SESSION_DISABLE_INIT', true); + if (!static::isInstalled()) { + SessionManager::disable(); } Registry::set('application', $this); - PKPString::init(); - $microTime = Core::microtime(); Registry::set('system.debug.startTime', $microTime); - $notes = []; - Registry::set('system.debug.notes', $notes); + $this->initializeLaravelContainer(); + PKPString::initialize(); - if (Config::getVar('general', 'installed')) { - $this->initializeLaravelContainer(); - } + // Load default locale files + Locale::registerPath(BASE_SYS_DIR . '/lib/pkp/locale'); } /** * Initialize Laravel container and register service providers */ - public function initializeLaravelContainer() + public function initializeLaravelContainer(): void { // Ensure multiple calls to this function don't cause trouble static $containerInitialized = false; @@ -229,10 +231,45 @@ public function initializeLaravelContainer() $laravelContainer = new PKPContainer(); $laravelContainer->registerConfiguredProviders(); + $this->initializeTimeZone(); + if (Config::getVar('database', 'debug')) { - \Illuminate\Support\Facades\DB::listen(function ($query) { - error_log("Database query\n{$query->sql}\n" . json_encode($query->bindings)); - }); + DB::listen(fn(QueryExecuted $query) => error_log("Database query\n{$query->sql}\n" . json_encode($query->bindings))); + } + } + + /** + * Setup the internal time zone for the database and PHP. + */ + protected function initializeTimeZone(): void + { + $timeZone = null; + // Loads the time zone from the configuration file + if ($setting = Config::getVar('general', 'time_zone')) { + try { + $timeZone = (new DateTimeZone($setting))->getName(); + } catch (Exception $e) { + $setting = strtolower($setting); + foreach (DateTimeZone::listIdentifiers() as $identifier) { + // Backward compatibility identification + if ($setting == strtolower(preg_replace(['/^\w+\//', '/_/'], ['', ' '], $identifier))) { + $timeZone = $identifier; + break; + } + } + } + } + // Set the default timezone + date_default_timezone_set($timeZone ?: ini_get('date.timezone') ?: 'UTC'); + + // Synchronize the database time zone + if (Application::isInstalled()) { + // Retrieve the current offset + $offset = (new DateTime())->format('P'); + $statement = DB::connection() instanceof MySqlConnection + ? "SET time_zone = '${offset}'" + : "SET TIME ZONE INTERVAL '${offset}' HOUR TO MINUTE"; + DB::statement($statement); } } @@ -259,13 +296,14 @@ public static function get() /** * Return a HTTP client implementation. * - * @return \GuzzleHttp\Client + * @return Client */ public function getHttpClient() { $application = Application::get(); $userAgent = $application->getName() . '/'; - if (Config::getVar('general', 'installed') && !defined('RUNNING_UPGRADE')) { + if (static::isInstalled() && !static::isUpgrading()) { + /** @var \PKP\site\VersionDAO */ $versionDao = DAORegistry::getDAO('VersionDAO'); $currentVersion = $versionDao->getCurrentVersion(); $userAgent .= $currentVersion->getVersionString(); @@ -273,7 +311,7 @@ public function getHttpClient() $userAgent .= '?'; } - return new \GuzzleHttp\Client([ + return new Client([ 'proxy' => [ 'http' => Config::getVar('proxy', 'http_proxy', null), 'https' => Config::getVar('proxy', 'https_proxy', null), @@ -416,7 +454,7 @@ public function &getEnabledProducts($category = null, $mainContextId = null) $settingContext = array_combine($this->getContextList(), $settingContext); } - $versionDao = DAORegistry::getDAO('VersionDAO'); /** @var VersionDAO $versionDao */ + $versionDao = DAORegistry::getDAO('VersionDAO'); /** @var \PKP\site\VersionDAO $versionDao */ $this->enabledProducts[$mainContextId] = $versionDao->getCurrentProducts($settingContext); } @@ -511,7 +549,6 @@ public function getDAOMap() 'SubmissionKeywordEntryDAO' => 'PKP\submission\SubmissionKeywordEntryDAO', 'SubmissionSubjectDAO' => 'PKP\submission\SubmissionSubjectDAO', 'SubmissionSubjectEntryDAO' => 'PKP\submission\SubmissionSubjectEntryDAO', - 'TimeZoneDAO' => 'PKP\i18n\TimeZoneDAO', 'TemporaryFileDAO' => 'PKP\file\TemporaryFileDAO', 'UserGroupAssignmentDAO' => 'PKP\security\UserGroupAssignmentDAO', 'UserGroupDAO' => 'PKP\security\UserGroupDAO', @@ -782,12 +819,11 @@ public function getCCLicenseBadge($ccLicenseURL, $locale = null) '|http[s]?://(www\.)?creativecommons.org/licenses/by-sa/3.0[/]?|' => 'submission.license.cc.by-sa3.footer' ]; if ($locale === null) { - $locale = AppLocale::getLocale(); + $locale = Locale::getLocale(); } foreach ($licenseKeyMap as $pattern => $key) { if (preg_match($pattern, $ccLicenseURL)) { - PKPLocale::requireComponents(LOCALE_COMPONENT_PKP_SUBMISSION, $locale); return __($key, [], $locale); } } @@ -843,7 +879,6 @@ public static function getWorkflowTypeRoles() */ public static function getWorkflowStageName($stageId) { - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_SUBMISSION, LOCALE_COMPONENT_APP_SUBMISSION); switch ($stageId) { case WORKFLOW_STAGE_ID_SUBMISSION: return 'submission.submission'; case WORKFLOW_STAGE_ID_INTERNAL_REVIEW: return 'workflow.review.internalReview'; @@ -863,7 +898,6 @@ public static function getWorkflowStageName($stageId) */ public static function getWorkflowStageColor($stageId) { - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_SUBMISSION, LOCALE_COMPONENT_APP_SUBMISSION); switch ($stageId) { case WORKFLOW_STAGE_ID_SUBMISSION: return '#d00a0a'; case WORKFLOW_STAGE_ID_INTERNAL_REVIEW: return '#e05c14'; @@ -931,6 +965,41 @@ public static function getMetadataFields() 'citations', ]; } + + /** + * Retrieves whether the application is installed + */ + public static function isInstalled(): bool + { + return !!Config::getVar('general', 'installed'); + } + + /** + * Retrieves whether the application is running an upgrade + */ + public static function isUpgrading(): bool + { + return defined('RUNNING_UPGRADE'); + } + + /** + * Retrieves whether the application is under maintenance (not installed or being upgraded) + */ + public static function isUnderMaintenance(): bool + { + return !static::isInstalled() || static::isUpgrading(); + } + + /** + * Signals the application is undergoing an upgrade + */ + public static function upgrade(): void + { + // Constant kept for backwards compatibility + if (!defined('RUNNING_UPGRADE')) { + define('RUNNING_UPGRADE', true); + } + } } define('REALLY_BIG_NUMBER', 10000); diff --git a/classes/core/PKPComponentRouter.inc.php b/classes/core/PKPComponentRouter.inc.php index cbf4b54d6b2..5645acc5063 100644 --- a/classes/core/PKPComponentRouter.inc.php +++ b/classes/core/PKPComponentRouter.inc.php @@ -61,7 +61,6 @@ define('COMPONENT_ROUTER_PARTS_MAXLENGTH', 50); define('COMPONENT_ROUTER_PARTS_MINLENGTH', 2); -use APP\i18n\AppLocale; use Exception; use PKP\config\Config; @@ -408,11 +407,6 @@ public function handleAuthorizationFailure( $authorizationMessage, array $messageParams = [] ) { - // Translate the authorization error message. - if (defined('LOCALE_COMPONENT_APP_COMMON')) { - AppLocale::requireComponents(LOCALE_COMPONENT_APP_COMMON); - } - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_USER); $translatedAuthorizationMessage = __($authorizationMessage, $messageParams); // Add the router name and operation if show_stacktrace is enabled. diff --git a/classes/core/PKPContainer.inc.php b/classes/core/PKPContainer.inc.php index dcfaeb88f13..68481c2214b 100644 --- a/classes/core/PKPContainer.inc.php +++ b/classes/core/PKPContainer.inc.php @@ -29,10 +29,8 @@ use Illuminate\Support\Facades\Facade; use PKP\config\Config; use PKP\Domains\Jobs\Providers\JobServiceProvider; -use PKP\i18n\PKPLocale; +use PKP\i18n\LocaleServiceProvider; use PKP\Support\ProxyParser; -use Sokil\IsoCodes\IsoCodesFactory; -use Sokil\IsoCodes\TranslationDriver\GettextExtensionDriver; use Throwable; @@ -93,13 +91,6 @@ public function renderForConsole($output, Throwable $e) Kernel::class ); - // This singleton is necessary to keep user selected language across the application - $this->singleton(IsoCodesFactory::class, function () { - $driver = new GettextExtensionDriver(); - $driver->setLocale(PKPLocale::getLocale()); - return new IsoCodesFactory(null, $driver); - }); - $this->singleton( 'queue.failer', function ($app) { @@ -131,6 +122,10 @@ public function registerConfiguredProviders() $this->register(new MailServiceProvider($this)); $this->register(new AppServiceProvider($this)); $this->register(new JobServiceProvider($this)); + $this->register(new \Illuminate\Cache\CacheServiceProvider($this)); + $this->register(new \Illuminate\Filesystem\FilesystemServiceProvider($this)); + $this->register(new \ElcoBvg\Opcache\ServiceProvider($this)); + $this->register(new LocaleServiceProvider($this)); } /** @@ -153,8 +148,15 @@ public function registerCoreContainerAliases() foreach ([ 'app' => [self::class, \Illuminate\Contracts\Container\Container::class, \Psr\Container\ContainerInterface::class], 'config' => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class], + 'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class], + 'cache.store' => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class, \Psr\SimpleCache\CacheInterface::class], + 'cache.psr6' => [\Symfony\Component\Cache\Adapter\Psr16Adapter::class, \Symfony\Component\Cache\Adapter\AdapterInterface::class, \Psr\Cache\CacheItemPoolInterface::class], 'db' => [\Illuminate\Database\DatabaseManager::class, \Illuminate\Database\ConnectionResolverInterface::class], 'db.connection' => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class], + 'files' => [\Illuminate\Filesystem\Filesystem::class], + 'filesystem' => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class], + 'filesystem.disk' => [\Illuminate\Contracts\Filesystem\Filesystem::class], + 'filesystem.cloud' => [\Illuminate\Contracts\Filesystem\Cloud::class], 'maps' => [MapContainer::class, MapContainer::class], 'events' => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class], 'queue' => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class], @@ -240,7 +242,19 @@ protected function loadConfiguration() $items['mail']['default'] = static::getDefaultMailer(); - $this->instance('config', new Repository($items)); // create instance and bind to use globally + // Cache configuration + $items['cache'] = [ + 'default' => 'opcache', + 'stores' => [ + 'opcache' => [ + 'driver' => 'opcache', + 'path' => Core::getBaseDir() . '/cache/opcache' + ] + ] + ]; + + // Create instance and bind to use globally + $this->instance('config', new Repository($items)); } /** diff --git a/classes/core/PKPPageRouter.inc.php b/classes/core/PKPPageRouter.inc.php index 66a287e7101..23a17200bb5 100644 --- a/classes/core/PKPPageRouter.inc.php +++ b/classes/core/PKPPageRouter.inc.php @@ -19,7 +19,7 @@ define('ROUTER_DEFAULT_OP', 'index'); use APP\core\Application; -use APP\i18n\AppLocale; +use PKP\facades\Locale; use PKP\config\Config; use PKP\db\DAORegistry; use PKP\plugins\HookRegistry; @@ -79,10 +79,10 @@ public function getCacheablePages() */ public function isCacheable($request, $testOnly = false) { - if (defined('SESSION_DISABLE_INIT') && !$testOnly) { + if (SessionManager::isDisabled() && !$testOnly) { return false; } - if (!Config::getVar('general', 'installed')) { + if (Application::isUnderMaintenance()) { return false; } if (!empty($_POST) || Validation::isLoggedIn()) { @@ -179,14 +179,14 @@ public function getCacheFilename($request) if (!isset($this->_cacheFilename)) { if ($request->isPathInfoEnabled()) { $id = $_SERVER['PATH_INFO'] ?? 'index'; - $id .= '-' . AppLocale::getLocale(); + $id .= '-' . Locale::getLocale(); } else { $id = ''; $application = $this->getApplication(); foreach ($application->getContextList() as $contextName) { $id .= $request->getUserVar($contextName) . '-'; } - $id .= $request->getUserVar('page') . '-' . $request->getUserVar('op') . '-' . $request->getUserVar('path') . '-' . AppLocale::getLocale(); + $id .= $request->getUserVar('page') . '-' . $request->getUserVar('op') . '-' . $request->getUserVar('path') . '-' . Locale::getLocale(); } $path = Core::getBaseDir(); $this->_cacheFilename = $path . '/cache/wc-' . md5($id) . '.html'; @@ -205,7 +205,7 @@ public function route($request) // If the application has not yet been installed we only // allow installer pages to be displayed. - if (!Config::getVar('general', 'installed')) { + if (!Application::isInstalled()) { if (!in_array($page, $this->getInstallationPages())) { // A non-installation page was called although // the system is not yet installed. Redirect to @@ -226,7 +226,7 @@ public function route($request) // Redirect requests from logged-out users to a context which is not // publicly enabled - if (!defined('SESSION_DISABLE_INIT')) { + if (!SessionManager::isDisabled()) { $user = $request->getUser(); $currentContext = $request->getContext(); if ($currentContext && !$currentContext->getEnabled() && !$user instanceof \PKP\user\User) { @@ -262,7 +262,7 @@ public function route($request) } } - if (!defined('SESSION_DISABLE_INIT')) { + if (!SessionManager::isDisabled()) { // Initialize session SessionManager::getManager(); } diff --git a/classes/core/PKPQueueProvider.inc.php b/classes/core/PKPQueueProvider.inc.php index 84e3932706a..19af5eee9c4 100644 --- a/classes/core/PKPQueueProvider.inc.php +++ b/classes/core/PKPQueueProvider.inc.php @@ -17,6 +17,7 @@ namespace PKP\core; +use APP\core\Application; use Illuminate\Queue\WorkerOptions; use PKP\config\Config; @@ -28,17 +29,16 @@ public function runJobsAtShutdown(): void { $disableRun = Config::getVar('queues', 'disable_jobs_run_at_shutdown', false); - if ($disableRun || defined('RUNNING_UPGRADE')) { + if ($disableRun || Application::isUnderMaintenance()) { return; } - $job = PKPJobModel - ::isAvailable() - ->notExceededAttempts() - ->nonEmptyQueue() - ->notQueue(PKPJobModel::TESTING_QUEUE) - ->limit(1) - ->first(); + $job = PKPJobModel::isAvailable() + ->notExceededAttempts() + ->nonEmptyQueue() + ->notQueue(PKPJobModel::TESTING_QUEUE) + ->limit(1) + ->first(); if ($job === null) { return; diff --git a/classes/core/PKPRequest.inc.php b/classes/core/PKPRequest.inc.php index 91ffeb3c4f4..578406f7df0 100644 --- a/classes/core/PKPRequest.inc.php +++ b/classes/core/PKPRequest.inc.php @@ -18,9 +18,14 @@ use APP\core\Application; use APP\facades\Repo; use PKP\config\Config; +use PKP\context\Context; use PKP\db\DAORegistry; use PKP\plugins\HookRegistry; +use PKP\security\Validation; +use PKP\session\Session; use PKP\session\SessionManager; +use PKP\site\Site; +use PKP\user\User; class PKPRequest { @@ -561,63 +566,44 @@ public function isRestfulUrlsEnabled() /** * Get site data. - * - * @return Site */ - public function &getSite() + public function getSite(): ?Site { $site = & Registry::get('site', true, null); - if ($site === null) { - $siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */ - $site = $siteDao->getSite(); - // PHP bug? This is needed for some reason or extra queries results. - Registry::set('site', $site); - } - - return $site; + return $site ??= DAORegistry::getDAO('SiteDAO')->getSite(); } /** * Get the user session associated with the current request. - * - * @return Session */ - public function &getSession() + public function getSession(): Session { $session = & Registry::get('session', true, null); - - if ($session === null) { - $sessionManager = SessionManager::getManager(); - $session = $sessionManager->getUserSession(); - } - - return $session; + return $session ??= SessionManager::getManager()->getUserSession(); } /** * Get the user associated with the current request. - * - * @return User */ - public function &getUser() + public function getUser(): ?User { $user = & Registry::get('user', true, null); - - $router = $this->getRouter(); - if (!is_null($handler = $router->getHandler()) && !is_null($token = $handler->getApiToken())) { - if ($user === null) { - $user = Repo::user()->getByApiKey($token); - } - if (is_null($user) || !$user->getData('apiKeyEnabled')) { - $user = null; - } + if ($user) { return $user; } - if ($user === null) { - $sessionManager = SessionManager::getManager(); - $session = $sessionManager->getUserSession(); - $user = $session->getUser(); + // Attempt to load user from API token + if (($handler = $this->getRouter()->getHandler()) + && ($token = $handler->getApiToken()) + && ($apiUser = Repo::user()->getByApiKey($token)) + && $apiUser->getData('apiKeyEnabled') + ) { + return $user = $apiUser; + } + + // Attempts to retrieve a logged user + if (Validation::isLoggedIn()) { + $user = SessionManager::getManager()->getUserSession()->getUser(); } return $user; @@ -640,12 +626,7 @@ public function getUserVar($key) // Get all vars (already cleaned) $vars = $this->getUserVars(); - - if (isset($vars[$key])) { - return $vars[$key]; - } else { - return null; - } + return $vars[$key] ?? null; } /** @@ -655,12 +636,7 @@ public function getUserVar($key) */ public function &getUserVars() { - if (!isset($this->_requestVars)) { - $this->_requestVars = array_map(function ($s) { - return is_string($s) ? trim($s) : $s; - }, array_merge($_GET, $_POST)); - } - + $this->_requestVars ??= array_map(fn($s) => is_string($s) ? trim($s) : $s, array_merge($_GET, $_POST)); return $this->_requestVars; } @@ -773,11 +749,9 @@ public function redirect($context = null, $page = null, $op = null, $path = null /** * Get the current "context" (press/journal/etc) object. * - * @return Context - * * @see PKPPageRouter::getContext() */ - public function &getContext() + public function &getContext(): ?Context { return $this->_delegateToRouter('getContext'); } diff --git a/classes/core/PKPString.inc.php b/classes/core/PKPString.inc.php index 706daa52330..fbc9aaae3d6 100644 --- a/classes/core/PKPString.inc.php +++ b/classes/core/PKPString.inc.php @@ -17,7 +17,6 @@ namespace PKP\core; use PKP\config\Config; - use Stringy\Stringy; class PKPString @@ -31,18 +30,16 @@ class PKPString /** * Perform initialization required for the string wrapper library. */ - public static function init() + public static function initialize() { - $clientCharset = strtolower_codesafe(Config::getVar('i18n', 'client_charset')); - - // Check if mbstring is installed - if (self::hasMBString() && !defined('ENABLE_MBSTRING')) { - // mbstring routines are available - define('ENABLE_MBSTRING', true); - - // Set up required ini settings for mbstring - // FIXME Do any other mbstring settings need to be set? - mb_internal_encoding($clientCharset); + static $isInitialized; + if (!$isInitialized) { + if (self::hasMBString()) { + // Set up default encoding + mb_internal_encoding('utf-8'); + ini_set('default_charset', 'utf-8'); + } + $isInitialized = true; } } @@ -65,8 +62,7 @@ public static function hasMBString() if (ini_get('mbstring.func_overload') && defined('MB_OVERLOAD_STRING')) { $hasMBString = false; } else { - $hasMBString = ( - extension_loaded('mbstring') && + $hasMBString = extension_loaded('mbstring') && function_exists('mb_strlen') && function_exists('mb_strpos') && function_exists('mb_strrpos') && @@ -74,8 +70,7 @@ function_exists('mb_substr') && function_exists('mb_strtolower') && function_exists('mb_strtoupper') && function_exists('mb_substr_count') && - function_exists('mb_send_mail') - ); + function_exists('mb_send_mail'); } return $hasMBString; } @@ -195,11 +190,10 @@ public static function substr_count($haystack, $needle) */ public static function encode_mime_header($string) { - if (defined('ENABLE_MBSTRING')) { - return mb_encode_mimeheader($string, mb_internal_encoding(), 'B', MAIL_EOL); - } else { - return $string; - } + static::initialize(); + return static::hasMBString() + ? mb_encode_mimeheader($string, mb_internal_encoding(), 'B', Core::isWindows() ? "\r\n" : "\n") + : $string; } // @@ -409,7 +403,7 @@ public static function stripUnsafeHtml($input) static $purifier; if (!isset($purifier)) { $config = \HTMLPurifier_Config::createDefault(); - $config->set('Core.Encoding', Config::getVar('i18n', 'client_charset')); + $config->set('Core.Encoding', 'utf-8'); $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); $config->set('HTML.Allowed', Config::getVar('security', 'allowed_html')); $config->set('Cache.SerializerPath', 'cache'); diff --git a/classes/emailTemplate/DAO.inc.php b/classes/emailTemplate/DAO.inc.php index 119f51a5356..05a6f1ccafe 100644 --- a/classes/emailTemplate/DAO.inc.php +++ b/classes/emailTemplate/DAO.inc.php @@ -18,6 +18,7 @@ use Illuminate\Support\LazyCollection; use PKP\core\EntityDAO; use PKP\db\XMLDAO; +use PKP\facades\Locale; use PKP\facades\Repo; class DAO extends EntityDAO @@ -322,11 +323,11 @@ public function installEmailTemplateLocaleData( ->where('locale', $locale) ->delete(); - $keyNotFoundHandler = function ($key) { - return null; - }; - $translatedSubject = __($subject, [], $locale, $keyNotFoundHandler); - $translatedBody = __($body, [], $locale, $keyNotFoundHandler); + $previous = Locale::getMissingKeyHandler(); + Locale::setMissingKeyHandler(fn (string $key): string => ''); + $translatedSubject = __($subject, [], $locale); + $translatedBody = __($body, [], $locale); + Locale::setMissingKeyHandler($previous); if ($translatedSubject !== null && $translatedBody !== null) { DB::table('email_templates_default_data')->insert([ 'email_key' => $attrs['key'], @@ -373,9 +374,7 @@ public function installEmailTemplateData( // Translate variable contents foreach ([&$subject, &$body, &$description] as &$var) { - $var = preg_replace_callback('{{translate key="([^"]+)"}}', function ($matches) use($locale) { - return __($matches[1], [], $locale); - }, $var); + $var = preg_replace_callback('{{translate key="([^"]+)"}}', fn($matches) => __($matches[1], [], $locale), $var); } if ($emailKey && $emailKey != $emailNode->getAttribute('key')) { diff --git a/classes/emailTemplate/Repository.inc.php b/classes/emailTemplate/Repository.inc.php index 3607a242ec5..bb53adfd713 100644 --- a/classes/emailTemplate/Repository.inc.php +++ b/classes/emailTemplate/Repository.inc.php @@ -14,7 +14,6 @@ namespace PKP\emailTemplate; use APP\emailTemplate\DAO; -use APP\i18n\AppLocale; use Illuminate\Support\LazyCollection; use PKP\core\PKPRequest; use PKP\plugins\HookRegistry; @@ -102,11 +101,6 @@ public function validate(?EmailTemplate $object, array $props, array $allowedLoc $this->schemaService->getValidationRules(PKPSchemaService::SCHEMA_EMAIL_TEMPLATE, $allowedLocales) ); - AppLocale::requireComponents( - LOCALE_COMPONENT_PKP_MANAGER, - LOCALE_COMPONENT_APP_MANAGER - ); - // Check required fields ValidatorFactory::required( $validator, diff --git a/classes/facades/Locale.inc.php b/classes/facades/Locale.inc.php new file mode 100644 index 00000000000..5811f36ea88 --- /dev/null +++ b/classes/facades/Locale.inc.php @@ -0,0 +1,55 @@ +getHttpClient(); + $response = $client->request('GET', $filenameOrUrl); + return Utils::streamFor($response->getBody()); + } elseif (file_exists($filenameOrUrl) && is_readable($filenameOrUrl)) { + $resource = fopen($filenameOrUrl, 'r'); + return Utils::streamFor($resource); + } + return null; + } /** * Read a file's contents. @@ -725,7 +750,6 @@ public function compressFile($filePath) */ private function _executeGzip($filePath, $decompress = false) { - PKPLocale::requireComponents(LOCALE_COMPONENT_PKP_ADMIN); $gzipPath = Config::getVar('cli', 'gzip'); if (!is_executable($gzipPath)) { throw new Exception(__('admin.error.executingUtil', ['utilPath' => $gzipPath, 'utilVar' => 'gzip'])); diff --git a/classes/filter/TemplateBasedFilter.inc.php b/classes/filter/TemplateBasedFilter.inc.php index d80d801fa23..7a8af7a15be 100644 --- a/classes/filter/TemplateBasedFilter.inc.php +++ b/classes/filter/TemplateBasedFilter.inc.php @@ -17,7 +17,7 @@ namespace PKP\filter; use APP\template\TemplateManager; - +use PKP\facades\Locale; class TemplateBasedFilter extends PersistableFilter { // @@ -71,7 +71,7 @@ public function addTemplateVars($templateMgr, &$input, $request, &$locale) public function &process(&$input) { // Initialize view - $locale = AppLocale::getLocale(); + $locale = Locale::getLocale(); $request = Application::get()->getRequest(); $templateMgr = TemplateManager::getManager($request); diff --git a/classes/filter/TypeDescriptionFactory.inc.php b/classes/filter/TypeDescriptionFactory.inc.php index 837ee879e1f..cc92a02a16d 100644 --- a/classes/filter/TypeDescriptionFactory.inc.php +++ b/classes/filter/TypeDescriptionFactory.inc.php @@ -127,10 +127,7 @@ public function _namespaceMap($namespace) self::TYPE_DESCRIPTION_NAMESPACE_XML => 'lib.pkp.classes.xslt.XMLTypeDescription', self::TYPE_DESCRIPTION_NAMESPACE_VALIDATOR => 'lib.pkp.classes.validation.ValidatorTypeDescription' ]; - if (!isset($namespaceMap[$namespace])) { - return null; - } - return $namespaceMap[$namespace]; + return $namespaceMap[$namespace] ?? null; } } diff --git a/classes/form/Form.inc.php b/classes/form/Form.inc.php index d1ea1344452..9fad62c1169 100644 --- a/classes/form/Form.inc.php +++ b/classes/form/Form.inc.php @@ -23,10 +23,11 @@ namespace PKP\form; use APP\core\Application; -use APP\i18n\AppLocale; +use PKP\facades\Locale; use APP\notification\NotificationManager; use APP\template\TemplateManager; +use PKP\session\SessionManager; use PKP\notification\PKPNotification; use PKP\plugins\HookRegistry; @@ -75,15 +76,15 @@ class Form public function __construct($template = null, $callHooks = true, $requiredLocale = null, $supportedLocales = null) { if ($requiredLocale === null) { - $requiredLocale = AppLocale::getPrimaryLocale(); + $requiredLocale = Locale::getPrimaryLocale(); } $this->requiredLocale = $requiredLocale; if ($supportedLocales === null) { - $supportedLocales = AppLocale::getSupportedFormLocales(); + $supportedLocales = Locale::getSupportedFormLocales(); } $this->supportedLocales = $supportedLocales; - $this->defaultLocale = AppLocale::getLocale(); + $this->defaultLocale = Locale::getLocale(); $this->_template = $template; $this->_data = []; @@ -299,7 +300,7 @@ public function validate($callHooks = true) } } - if (!defined('SESSION_DISABLE_INIT')) { + if (!SessionManager::isDisabled()) { $request = Application::get()->getRequest(); $user = $request->getUser(); @@ -495,11 +496,11 @@ public function _decomposeArray($name, $value, $stack) $returner .= $this->_decomposeArray($name, $subValue, $newStack); } } else { - $name = htmlentities($name, ENT_COMPAT, LOCALE_ENCODING); - $value = htmlentities($value, ENT_COMPAT, LOCALE_ENCODING); + $name = htmlentities($name, ENT_COMPAT); + $value = htmlentities($value, ENT_COMPAT); $returner .= '\n"; diff --git a/classes/form/FormBuilderVocabulary.inc.php b/classes/form/FormBuilderVocabulary.inc.php index e74878da2ab..acf65c39e28 100644 --- a/classes/form/FormBuilderVocabulary.inc.php +++ b/classes/form/FormBuilderVocabulary.inc.php @@ -359,7 +359,7 @@ public function _smartyFBVButton($params, $smarty) case 'disabled': $smarty->assign('FBV_' . $key, $value); break; - default: $buttonParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + default: $buttonParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -448,7 +448,7 @@ public function _smartyFBVTextInput($params, $smarty) } break; case 'placeholder': - $textInputParams .= 'placeholder="' . htmlspecialchars(__($value), ENT_QUOTES, LOCALE_ENCODING) . '" '; + $textInputParams .= 'placeholder="' . htmlspecialchars(__($value), ENT_QUOTES) . '" '; break; case 'disabled': case 'readonly': @@ -460,11 +460,11 @@ public function _smartyFBVTextInput($params, $smarty) $smarty->assign('FBV_' . $key, $value); break; case 'required': if ($value) { - $textInputParams .= 'required="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + $textInputParams .= 'required="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } break; default: - $textInputParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + $textInputParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -537,7 +537,7 @@ public function _smartyFBVTextArea($params, $smarty) } break; case 'id': break; // if we don't do this, the textarea ends up with two id attributes because FBV_id is also set. - default: $textAreaParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + default: $textAreaParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -568,7 +568,7 @@ public function _smartyFBVHiddenInput($params, $smarty) break; case 'label': break; case 'type': break; - default: $hiddenInputParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + default: $hiddenInputParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -620,7 +620,7 @@ public function _smartyFBVSelect($params, $smarty) break; case 'subLabelTranslate': break; case 'label': $smarty->assign('FBV_label_content', $this->_smartyFBVSubLabel($params, $smarty)); break; - default: $selectParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + default: $selectParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -660,7 +660,7 @@ public function _smartyFBVCheckboxGroup($params, $smarty) case 'type': break; case 'inline': break; case 'subLabelTranslate': break; - default: $checkboxParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + default: $checkboxParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -693,7 +693,7 @@ public function _smartyFBVCheckbox($params, $smarty) case 'disabled': $smarty->assign('FBV_' . $key, $value); break; - default: $checkboxParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + default: $checkboxParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -731,7 +731,7 @@ public function _smartyFBVRadioButton($params, $smarty) case 'content': $smarty->assign('FBV_' . $key, $value); break; - default: $radioParams .= htmlspecialchars($key, ENT_QUOTES, LOCALE_ENCODING) . '="' . htmlspecialchars($value, ENT_QUOTES, LOCALE_ENCODING) . '" '; + default: $radioParams .= htmlspecialchars($key, ENT_QUOTES) . '="' . htmlspecialchars($value, ENT_QUOTES) . '" '; } } @@ -937,7 +937,7 @@ public function smartyFieldLabel($params, $smarty) $returner = ''; if (isset($params) && !empty($params)) { if (isset($params['key'])) { - $params['label'] = __($params['key'], $params); + $params['label'] = __($params['key'], $params ?? []); } $form = $this->getForm(); diff --git a/classes/form/validation/FormValidatorLocale.inc.php b/classes/form/validation/FormValidatorLocale.inc.php index b8ade0adc10..f8065f6b977 100644 --- a/classes/form/validation/FormValidatorLocale.inc.php +++ b/classes/form/validation/FormValidatorLocale.inc.php @@ -15,7 +15,7 @@ namespace PKP\form\validation; -use APP\i18n\AppLocale; +use PKP\facades\Locale; class FormValidatorLocale extends FormValidator { @@ -36,7 +36,7 @@ public function __construct(&$form, $field, $type, $message, $requiredLocale = n { parent::__construct($form, $field, $type, $message, $validator); if ($requiredLocale === null) { - $requiredLocale = AppLocale::getPrimaryLocale(); + $requiredLocale = Locale::getPrimaryLocale(); } $this->_requiredLocale = $requiredLocale; } @@ -53,8 +53,7 @@ public function __construct(&$form, $field, $type, $message, $requiredLocale = n */ public function getMessage() { - $allLocales = AppLocale::getAllLocales(); - return parent::getMessage() . ' (' . $allLocales[$this->_requiredLocale] . ')'; + return parent::getMessage() . ' (' . Locale::getMetadata($this->_requiredLocale)->getDisplayName() . ')'; } // diff --git a/classes/handler/APIHandler.inc.php b/classes/handler/APIHandler.inc.php index 31f28cea362..7df76aa675e 100644 --- a/classes/handler/APIHandler.inc.php +++ b/classes/handler/APIHandler.inc.php @@ -17,7 +17,6 @@ use APP\core\Application; use APP\core\Services; -use APP\i18n\AppLocale; use PKP\config\Config; use PKP\core\APIResponse; use PKP\plugins\HookRegistry; @@ -30,8 +29,6 @@ use Slim\App; -AppLocale::requireComponents(LOCALE_COMPONENT_PKP_API, LOCALE_COMPONENT_APP_API); - class APIHandler extends PKPHandler { protected $_app; diff --git a/classes/handler/PKPHandler.inc.php b/classes/handler/PKPHandler.inc.php index b16b85b048d..d2082b0fcff 100644 --- a/classes/handler/PKPHandler.inc.php +++ b/classes/handler/PKPHandler.inc.php @@ -17,7 +17,6 @@ namespace PKP\handler; use APP\core\Application; -use APP\i18n\AppLocale; use APP\template\TemplateManager; use PKP\config\Config; use PKP\core\Dispatcher; @@ -29,7 +28,7 @@ use PKP\security\authorization\AuthorizationPolicy; use PKP\security\authorization\HttpsPolicy; use PKP\security\authorization\RestrictedSiteAccessPolicy; - +use PKP\session\SessionManager; use PKP\security\authorization\UserRolesRequiredPolicy; use PKP\security\Role; use PKP\security\Validation; @@ -328,8 +327,7 @@ public function authorize($request, &$args, $roleAssignments) // Ensure the allowed hosts setting, when provided, is respected. $this->addPolicy(new AllowedHostsPolicy($request), true); - - if (!defined('SESSION_DISABLE_INIT')) { + if (!SessionManager::isDisabled()) { // Add user roles in authorized context. $user = $request->getUser(); if ($user instanceof \PKP\user\User || $request->getRouter() instanceof \PKP\core\APIRouter) { @@ -455,7 +453,7 @@ public static function getRangeInfo($request, $rangeName, $contextData = null) $context = $request->getContext(); $pageNum = $request->getUserVar(self::getPageParamName($rangeName)); if (empty($pageNum)) { - $session = & $request->getSession(); + $session = $request->getSession(); $pageNum = 1; // Default to page 1 if ($session && $contextData !== null) { // See if we can get a page number from a prior request @@ -472,7 +470,7 @@ public static function getRangeInfo($request, $rangeName, $contextData = null) } } } else { - $session = & $request->getSession(); + $session = $request->getSession(); if ($session && $contextData !== null) { // Store the page number $contextHash = self::hashPageContext($request, $contextData); @@ -522,17 +520,7 @@ public function setupTemplate($request) } assert($request instanceof \PKP\core\PKPRequest); - AppLocale::requireComponents( - LOCALE_COMPONENT_PKP_COMMON, - LOCALE_COMPONENT_PKP_USER, - LOCALE_COMPONENT_APP_COMMON - ); - $userRoles = (array) $this->getAuthorizedContextObject(ASSOC_TYPE_USER_ROLES); - if (array_intersect([Role::ROLE_ID_MANAGER], $userRoles)) { - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_MANAGER); - } - $templateMgr = TemplateManager::getManager($request); $templateMgr->assign('userRoles', $userRoles); diff --git a/classes/i18n/Locale.inc.php b/classes/i18n/Locale.inc.php new file mode 100644 index 00000000000..d33dbe7469d --- /dev/null +++ b/classes/i18n/Locale.inc.php @@ -0,0 +1,437 @@ +translate($key, null, $params, $locale); + } + + /** + * @copy \Illuminate\Contracts\Translation\Translator::choice() + */ + public function choice($key, $number, array $params = [], $locale = null): string + { + return $this->translate($key, $number, $params, $locale); + } + + /** + * @copy \Illuminate\Contracts\Translation\Translator::getLocale() + */ + public function getLocale(): string + { + if (isset($this->locale)) { + return $this->locale; + } + $request = $this->_getRequest(); + $locale = $request->getUserVar('setLocale') + ?: (SessionManager::hasSession() ? SessionManager::getManager()->getUserSession()->getSessionVar('currentLocale') : null) + ?: $request->getCookieVar('currentLocale'); + $this->setLocale($locale); + return $this->locale; + } + + /** + * @copy \Illuminate\Contracts\Translation\Translator::setLocale() + */ + public function setLocale($locale): void + { + if (!$this->isLocaleValid($locale) || !$this->isSupported($locale)) { + if ($locale) { + error_log((string) new InvalidArgumentException("Invalid/unsupported locale \"${locale}\", default locale restored")); + } + $locale = $this->getPrimaryLocale(); + } + + $this->locale = $locale; + setlocale(LC_ALL, "${locale}.utf-8", $locale); + putenv("LC_ALL=${locale}"); + } + + /** + * @copy LocaleInterface::getPrimaryLocale() + */ + public function getPrimaryLocale(): string + { + if (isset($this->primaryLocale)) { + return $this->primaryLocale; + } + $request = $this->_getRequest(); + $locale = null; + if (SessionManager::isDisabled()) { + $locale = $this->getDefaultLocale(); + } elseif ($context = $request->getContext()) { + $locale = $context->getPrimaryLocale(); + } elseif ($site = $request->getSite()) { + $locale = $site->getPrimaryLocale(); + } + return $this->primaryLocale = $this->isLocaleValid($locale) + ? $locale + : $this->getDefaultLocale(); + } + + /** + * @copy LocaleInterface::registerPath() + */ + public function registerPath(string $path, int $priority = 0): void + { + $path = new SplFileInfo($path); + if (!$path->isDir()) { + throw new InvalidArgumentException("${path} isn't a valid folder"); + } + + // Invalidate the loaded bundles cache + $realPath = $path->getRealPath(); + if (($this->paths[$realPath] ?? null) !== $priority) { + $this->paths[$realPath] = $priority; + $this->localeBundles = []; + $this->locales = null; + } + } + + /** + * @copy LocaleInterface::registerLoader() + */ + public function registerLoader(callable $fileLoader, int $priority = 0): void + { + // Invalidate the loaded bundles cache + if (array_search($fileLoader, $this->loaders[$priority] ?? [], true) === false) { + $this->loaders[$priority][] = $fileLoader; + $this->localeBundles = []; + ksort($this->loaders, SORT_NUMERIC); + } + } + + /** + * @copy LocaleInterface::isLocaleValid() + */ + public function isLocaleValid(?string $locale): bool + { + return !empty($locale) && preg_match(LocaleInterface::LOCALE_EXPRESSION, $locale); + } + + /** + * @copy LocaleInterface::getMetadata() + */ + public function getMetadata(string $locale): ?LocaleMetadata + { + return $this->getLocales()[$locale] ?? null; + } + + /** + * @copy LocaleInterface::getLocales() + */ + public function getLocales(): array + { + $key = __METHOD__ . static::MAX_CACHE_LIFETIME . array_reduce(array_keys($this->paths), fn(string $hash, string $path): string => sha1($hash . $path), ''); + $expiration = DateInterval::createFromDateString(static::MAX_CACHE_LIFETIME); + return $this->locales ??= Cache::remember($key, $expiration, function () { + $locales = []; + foreach (array_keys($this->paths) as $folder) { + foreach (new DirectoryIterator($folder) as $cursor) { + if ($cursor->isDir() && $this->isLocaleValid($cursor->getBasename())) { + $locales[$cursor->getBasename()] ??= LocaleMetadata::create($cursor->getBasename()); + } + } + } + ksort($locales); + return $locales; + }); + } + + /** + * @copy LocaleInterface::installLocale() + */ + public function installLocale(string $locale): void + { + Repo::emailTemplate()->dao->installEmailTemplateLocaleData(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), [$locale]); + + // Load all plugins so they can add locale data if needed + $categories = PluginRegistry::getCategories(); + foreach ($categories as $category) { + PluginRegistry::loadCategory($category); + } + HookRegistry::call('Locale::installLocale', [&$locale]); + } + + /** + * @copy LocaleInterface::uninstallLocale() + */ + public function uninstallLocale(string $locale): void + { + // Delete locale-specific data + Repo::emailTemplate()->dao->deleteEmailTemplatesByLocale($locale); + Repo::emailTemplate()->dao->deleteDefaultEmailTemplatesByLocale($locale); + } + + /** + * Retrieves whether the given locale is supported + */ + public function isSupported(string $locale): bool + { + static $locales; + $locales ??= SessionManager::isDisabled() + ? array_keys($this->getLocales()) + : (($context = $this->_getRequest()->getContext()) ? $context->getSupportedLocales() : $this->_getRequest()->getSite()->getSupportedLocales()); + return in_array($locale, $locales); + } + + /** + * @copy LocaleInterface::getSupportedFormLocales() + */ + public function getSupportedFormLocales(): array + { + return $this->supportedFormLocales ??= (fn(): array => SessionManager::isDisabled() + ? array_map(fn(LocaleMetadata $locale) => $locale->locale, $this->getLocales()) + : (($context = $this->_getRequest()->getContext()) ? $context->getSupportedFormLocaleNames() : $this->_getRequest()->getSite()->getSupportedLocaleNames()) + )(); + } + + /** + * @copy LocaleInterface::getSupportedLocales() + */ + public function getSupportedLocales(): array + { + return $this->supportedLocales ??= (fn(): array => SessionManager::isDisabled() + ? array_map(fn(LocaleMetadata $locale) => $locale->locale, $this->getLocales()) + : ($this->_getRequest()->getContext() ?? $this->_getRequest()->getSite())->getSupportedLocaleNames() + )(); + } + + /** + * @copy LocaleInterface::setMissingKeyHandler() + */ + public function setMissingKeyHandler(?callable $handler): void + { + $this->missingKeyHandler = $handler; + } + + /** + * @copy LocaleInterface::getMissingKeyHandler() + */ + public function getMissingKeyHandler(): ?callable + { + return $this->missingKeyHandler; + } + + /** + * @copy LocaleInterface::getBundle() + */ + public function getBundle(?string $locale = null, bool $useCache = true): LocaleBundle + { + $locale ??= $this->getLocale(); + $getter = function () use ($locale): LocaleBundle { + $bundle = []; + foreach ($this->paths as $folder => $priority) { + $bundle += $this->_getLocaleFiles($folder, $locale, $priority); + } + foreach ($this->loaders as $loader) { + $loader($locale, $bundle); + } + return new LocaleBundle($locale, $bundle); + }; + return $useCache ? $this->localeBundles[$locale] ??= $getter() : $getter(); + } + + /** + * @copy LocaleInterface::getDefaultLocale() + */ + public function getDefaultLocale(): string + { + return Config::getVar('i18n', 'locale'); + } + + /** + * @copy LocaleInterface::getCountries() + */ + public function getCountries(?string $locale = null): Countries + { + return $this->_getLocaleCache(__METHOD__, $locale, fn () => $this->_getIsoCodes($locale)->getCountries()); + } + + /** + * @copy LocaleInterface::getCurrencies() + */ + public function getCurrencies(?string $locale = null): Currencies + { + return $this->_getLocaleCache(__METHOD__, $locale, fn () => $this->_getIsoCodes($locale)->getCurrencies()); + } + + /** + * @copy LocaleInterface::getLanguages() + */ + public function getLanguages(?string $locale = null): LanguagesInterface + { + return $this->_getLocaleCache(__METHOD__, $locale, fn () => $this->_getIsoCodes($locale)->getLanguages()); + } + + /** + * @copy LocaleInterface::getScripts() + */ + public function getScripts(?string $locale = null): Scripts + { + return $this->_getLocaleCache(__METHOD__, $locale, fn () => $this->_getIsoCodes($locale)->getScripts()); + } + + + /** + * Translates the texts + */ + protected function translate(string $key, ?int $number, array $params, ?string $locale): string + { + if (($key = trim($key)) === '') { + return ''; + } + + $locale ??= $this->getLocale(); + $localeBundle = $this->getBundle($locale); + $value = $number === null ? $localeBundle->translateSingular($key, $params) : $localeBundle->translatePlural($key, $number, $params); + if ($value ?? HookRegistry::call('Locale::translate', [&$value, $key, $params, $number, $locale, $localeBundle])) { + return $value; + } + + error_log("Missing locale key \"${key}\" for the locale \"${locale}\""); + return is_callable($this->missingKeyHandler) ? ($this->missingKeyHandler)($key) : '##' . htmlentities($key) . '##'; + } + + /** + * Retrieves a cached item only if it belongs to the current locale. If it doesn't exist, the getter will be called + */ + private function _getLocaleCache(string $key, ?string $locale, callable $getter) + { + if (($locale ??= $this->getLocale()) !== $this->getLocale()) { + return $getter(); + } + if (!isset($this->cache[$key][$locale])) { + // Ensures the previous cache is cleared + $this->cache[$key] = [$locale => $getter()]; + } + return $this->cache[$key][$locale]; + } + + /** + * Given a locale folder, retrieves all locale files (.po) + * + * @return int[] + */ + private function _getLocaleFiles(string $folder, string $locale, int $priority): array + { + $files = $this->localeFiles[$folder][$locale] ?? null; + if ($files === null) { + $files = []; + if (is_dir($path = "${folder}/${locale}")) { + $directory = new RecursiveDirectoryIterator($path); + $iterator = new RecursiveIteratorIterator($directory); + $files = array_keys(iterator_to_array(new RegexIterator($iterator, '/\.po$/i', RecursiveRegexIterator::GET_MATCH))); + } + $this->localeFiles[$folder][$locale] = $files; + } + return array_fill_keys($files, $priority); + } + + /** + * Retrieves the request + */ + private function _getRequest(): PKPRequest + { + return app(PKPRequest::class); + } + + /** + * Retrieves the ISO codes factory + */ + private function _getIsoCodes(string $locale = null): IsoCodesFactory + { + return app(IsoCodesFactory::class, $locale ? ['locale' => $locale] : []); + } +} diff --git a/classes/i18n/LocaleConversion.inc.php b/classes/i18n/LocaleConversion.inc.php new file mode 100644 index 00000000000..d55ea6b149a --- /dev/null +++ b/classes/i18n/LocaleConversion.inc.php @@ -0,0 +1,122 @@ + $locale->getIsoAlpha2() === $iso2Letter); + return $locale ? $locale->getIsoAlpha3() : null; + } + + /** + * Translate the ISO 3-letter language string (ISO639-2b) into a ISO compatible 2-letter string (ISO639-1). + */ + public static function get2LetterFrom3LetterIsoLanguage(?string $iso3Letter): ?string + { + assert(strlen($iso3Letter) === 3); + $locale = Arr::first(Locale::getLocales(), fn(LocaleMetadata $locale) => $locale->getIsoAlpha3() === $iso3Letter); + return $locale ? $locale->getIsoAlpha2() : null; + } + + /** + * Translate the PKP locale identifier into an ISO639-2b compatible 3-letter string. + */ + public static function get3LetterIsoFromLocale(?string $locale): ?string + { + assert(strlen($locale) >= 5); + $iso2Letter = substr($locale, 0, 2); + return static::get3LetterFrom2LetterIsoLanguage($iso2Letter); + } + + /** + * Translate an ISO639-2b compatible 3-letter string into the PKP locale identifier. + * This can be ambiguous if several locales are defined for the same language. In this case we'll use the primary locale to disambiguate. + * If that still doesn't determine a unique locale then we'll choose the first locale found. + */ + public static function getLocaleFrom3LetterIso(?string $iso3Letter): ?string + { + assert(strlen($iso3Letter) === 3); + $primaryLocale = Locale::getPrimaryLocale(); + + $candidates = []; + foreach (Locale::getLocales() as $identifier => $locale) { + if ($locale->getIsoAlpha3() === $iso3Letter) { + if ($identifier === $primaryLocale) { + // In case of ambiguity the primary locale overrides all other options so we're done. + return $primaryLocale; + } + $candidates[$identifier] = true; + } + } + + // Attempts to retrieve the first matching locale which is in the supported list, otherwise defaults to the first found candidate + return Arr::first(array_keys(Locale::getSupportedLocales()), fn(string $locale) => $candidates[$locale] ?? false, array_key_first($candidates)); + } + + /** + * Translate the ISO 2-letter language string (ISO639-1) into ISO639-3. + */ + public static function getIso3FromIso1(?string $iso1): ?string + { + assert(strlen($iso1) === 2); + $locale = Arr::first(Locale::getLocales(), fn(LocaleMetadata $locale) => $locale->getIsoAlpha2() === $iso1); + return $locale ? $locale->getIsoAlpha3() : null; + } + + /** + * Translate the ISO639-3 into ISO639-1. + */ + public static function getIso1FromIso3(?string $iso3): ?string + { + assert(strlen($iso3) === 3); + $locale = Arr::first(Locale::getLocales(), fn(LocaleMetadata $locale) => $locale->getIsoAlpha3() === $iso3); + return $locale ? $locale->getIsoAlpha2() : null; + } + + /** + * Translate the PKP locale identifier into an ISO639-3 compatible 3-letter string. + */ + public static function getIso3FromLocale(?string $locale): ?string + { + assert(strlen($locale) >= 5); + $iso1 = substr($locale, 0, 2); + return static::getIso3FromIso1($iso1); + } + + /** + * Translate the PKP locale identifier into an ISO639-1 compatible 2-letter string. + */ + public static function getIso1FromLocale(?string $locale): string + { + assert(strlen($locale) >= 5); + return substr($locale, 0, 2); + } +} diff --git a/classes/i18n/LocaleFile.inc.php b/classes/i18n/LocaleFile.inc.php deleted file mode 100644 index c06bdc34308..00000000000 --- a/classes/i18n/LocaleFile.inc.php +++ /dev/null @@ -1,236 +0,0 @@ -locale = $locale; - $this->filename = $filename; - } - - /** - * Get the cache object for this locale file. - */ - public function _getCache($locale) - { - if (!isset($this->cache)) { - $cacheManager = CacheManager::getManager(); - $this->cache = $cacheManager->getFileCache( - 'locale', - md5($this->filename), - [$this, '_cacheMiss'] - ); - - // Check to see if the cache is outdated. - // Only some kinds of caches track cache dates; - // if there's no date available (ie cachedate is - // null), we have to assume it's up to date. - $cacheTime = $this->cache->getCacheTime(); - if ($cacheTime === null || $cacheTime < filemtime($this->filename)) { - // This cache is out of date; flush it. - $this->cache->setEntireCache(self::load($this->filename)); - } - } - return $this->cache; - } - - /** - * Register a cache miss. - */ - public function _cacheMiss($cache, $id) - { - return null; // It's not in this locale file. - } - - /** - * Get the filename for this locale file. - */ - public function getFilename() - { - return $this->filename; - } - - /** - * Translate a string using the selected locale. - * Substitution works by replacing tokens like "{$foo}" with the value of - * the parameter named "foo" (if supplied). - * - * @param string $key - * @param array $params named substitution parameters - * @param string $locale the locale to use - * - * @return string - */ - public function translate($key, $params = [], $locale = null) - { - if ($this->isValid()) { - $key = trim($key); - if (empty($key)) { - return ''; - } - - $cache = $this->_getCache($this->locale); - $message = $cache->get($key); - if (!isset($message)) { - // Try to force loading the plugin locales. - $message = $this->_cacheMiss($cache, $key); - } - - if (isset($message)) { - if (!empty($params)) { - // Substitute custom parameters - foreach ($params as $key => $value) { - $message = str_replace("{\$${key}}", (string) $value, $message); - } - } - - // if client encoding is set to iso-8859-1, transcode string from utf8 since we store all XML files in utf8 - if (LOCALE_ENCODING == 'iso-8859-1') { - $message = utf8_decode($message); - } - - return $message; - } - } - return null; - } - - /** - * Static method: Load a locale array from a file. Not cached! - * - * @param string $filename Filename to locale .po file to load - * - * @return array - */ - public static function &load($filename) - { - $localeData = []; - $loader = new \Gettext\Loader\PoLoader(); - foreach ($loader->loadFile($filename) as $translation) { - $localeData[$translation->getOriginal()] = $translation->getTranslation(); - } - return $localeData; - } - - /** - * Check if a locale is valid. - * - * @return bool - */ - public function isValid() - { - return isset($this->locale) && file_exists($this->filename); - } - - /** - * Test a locale file against the given reference locale file and - * return an array of errorType => array(errors). - * - * @param object $referenceLocaleFile - * - * @return array - */ - public function testLocale(&$referenceLocaleFile) - { - $errors = [ - LOCALE_ERROR_MISSING_KEY => [], - LOCALE_ERROR_EXTRA_KEY => [], - LOCALE_ERROR_DIFFERING_PARAMS => [], - LOCALE_ERROR_MISSING_FILE => [] - ]; - - if ($referenceLocaleFile->isValid()) { - if (!$this->isValid()) { - $errors[LOCALE_ERROR_MISSING_FILE][] = [ - 'locale' => $this->locale, - 'filename' => $this->filename - ]; - return $errors; - } - } else { - // If the reference file itself does not exist or is invalid then - // there's nothing to be translated here. - return $errors; - } - - $localeContents = self::load($this->filename); - $referenceContents = self::load($referenceLocaleFile->filename); - - foreach ($referenceContents as $key => $referenceValue) { - if (!isset($localeContents[$key])) { - $errors[LOCALE_ERROR_MISSING_KEY][] = [ - 'key' => $key, - 'locale' => $this->locale, - 'filename' => $this->filename, - 'reference' => $referenceValue - ]; - continue; - } - $value = $localeContents[$key]; - - $referenceParams = AppLocale::getParameterNames($referenceValue); - $params = AppLocale::getParameterNames($value); - if (count(array_diff($referenceParams, $params)) > 0) { - $errors[LOCALE_ERROR_DIFFERING_PARAMS][] = [ - 'key' => $key, - 'locale' => $this->locale, - 'mismatch' => array_diff($referenceParams, $params), - 'filename' => $this->filename, - 'reference' => $referenceValue, - 'value' => $value - ]; - } - // After processing a key, remove it from the list; - // this way, the remainder at the end of the loop - // will be extra unnecessary keys. - unset($localeContents[$key]); - } - - // Leftover keys are extraneous. - foreach ($localeContents as $key => $value) { - $errors[LOCALE_ERROR_EXTRA_KEY][] = [ - 'key' => $key, - 'locale' => $this->locale, - 'filename' => $this->filename - ]; - } - - return $errors; - } -} - -if (!PKP_STRICT_MODE) { - class_alias('\PKP\i18n\LocaleFile', '\LocaleFile'); -} diff --git a/classes/i18n/LocaleMetadata.inc.php b/classes/i18n/LocaleMetadata.inc.php new file mode 100644 index 00000000000..52de43d9dfa --- /dev/null +++ b/classes/i18n/LocaleMetadata.inc.php @@ -0,0 +1,171 @@ +locale = $locale; + return $instance; + } + + /** + * Retrieves the display name + */ + public function getDisplayName(?string $locale = null): string + { + $country = $this->getCountry($locale); + // Remove parenthesis from the language name and also convert the its character to uppercase (due to the IsoCodes texts) + return PKPString::regexp_replace('/\s*\([^)]*\)\s*/', '', PKPString::ucfirst($this->_getLanguage($locale)->getLocalName())) . ($country ? " (${country})" : ''); + } + + /** + * Retrieves the language name + */ + public function getLanguage(?string $locale = null): string + { + return $this->_getLanguage($locale)->getLocalName(); + } + + /** + * Retrieves the country name + */ + public function getCountry(?string $locale = null): ?string + { + return $this->_parse()->country ? Locale::getCountries($locale)->getByAlpha2($this->_parse()->country)->getLocalName() : null; + } + + /** + * Retrieves the script name + */ + public function getScript(?string $locale = null): ?string + { + $script = ucfirst($this->_parse()->script); + return $this->_parse()->script ? Locale::getScripts($locale)->getByAlpha4($script)->getLocalName() : null; + } + + /** + * Whether the locale expects text on the right-to-left format + */ + public function isRightToLeft(): bool + { + $locale = $this->_parse(); + $language = strtolower($locale->language ?? ''); + $script = strtolower($locale->script ?? ''); + $rightToLeftLanguages = array_fill_keys(['ar', 'dv', 'fa', 'he', 'ku', 'nqo', 'prs', 'ps', 'sd', 'syr', 'ug', 'ur', 'yi'], true); + $languageScriptExceptions = ['sd-deva' => false, 'tzm-arab' => true, 'pa-arab' => true]; + return $languageScriptExceptions["${language}-${script}"] + ?? $rightToLeftLanguages[$language] + ?? $languageScriptExceptions[$this->getIsoAlpha3() . "-${script}"] + ?? $rightToLeftLanguages[$this->getIsoAlpha3()] + ?? false; + } + + /** + * Compares two locales and retrieves the completeness ratio (source locale keys which are present in the reference) + * If a locale isn't supplied, LocaleInterface::DEFAULT_LOCALE will be used as reference + */ + public function getCompletenessRatio(string $referenceLocale = null): float + { + $destiny = Locale::getBundle($referenceLocale ?? LocaleInterface::DEFAULT_LOCALE)->getTranslator()->getEntries(); + $source = Locale::getBundle($this->locale, false)->getTranslator()->getEntries(); + $intersection = array_intersect_key($source, $destiny); + return min(1, count($intersection) / max(1, count($destiny))); + } + + /** + * Retrieves whether the locale can be considered complete respecting a threshold level of completeness + */ + public function isComplete(float $minimumThreshold = 0.9, ?string $referenceLocale = null): bool + { + return $this->getCompletenessRatio($referenceLocale) >= $minimumThreshold; + } + + /** + * Retrieves the ISO639-2b representation + */ + public function getIsoAlpha2(): string + { + return $this->_getLanguage()->getAlpha2(); + } + + /** + * Retrieves the ISO639-3 representation + */ + public function getIsoAlpha3(): string + { + return $this->_getLanguage()->getAlpha3(); + } + + /** + * Private constructor + */ + private function __construct() + { + } + + /** + * Retrieves the language + */ + private function _getLanguage(?string $locale = null): ?Language + { + return Locale::getLanguages($locale)->getByAlpha2($this->_parse()->language); + } + + /** + * Parses the locale string and retrieve its pieces + */ + private function _parse(): object + { + if (isset($this->_parsedLocale)) { + return $this->_parsedLocale; + } + if (!preg_match(LocaleInterface::LOCALE_EXPRESSION, $this->locale, $matches)) { + throw new DomainException("Invalid locale \"{$this->locale}\""); + } + return $this->_parsedLocale = (object) [ + 'language' => $matches['language'], + 'country' => $matches['country'] ?? null, + // Updates our script definitions to match the ISO 15924 + 'script' => isset($matches['script']) ? str_replace(['cyrillic', 'latin'], ['latn', 'cyrl'], strtolower($matches['script'])) : null + ]; + } +} diff --git a/classes/i18n/LocaleServiceProvider.inc.php b/classes/i18n/LocaleServiceProvider.inc.php new file mode 100644 index 00000000000..a44a55cbb93 --- /dev/null +++ b/classes/i18n/LocaleServiceProvider.inc.php @@ -0,0 +1,66 @@ +app->singleton(LocaleInterface::class, fn () => $this->app->make(Locale::class)); + // Replaces the default Laravel translator + $this->app->alias(LocaleInterface::class, 'translator'); + $this->app->alias(LocaleInterface::class, Translator::class); + + // Reuses the instance and keeps the user selected language across the application + $this->app->singleton( + IsoCodesFactory::class, + fn (Container $container, array $params): IsoCodesFactory => new IsoCodesFactory( + null, + new IsoCodesTranslationDriver($params['locale'] ?? LocaleFacade::getLocale()) + ) + ); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides(): array + { + return [LocaleInterface::class, Translator::class, 'translator']; + } +} diff --git a/classes/i18n/PKPLocale.inc.php b/classes/i18n/PKPLocale.inc.php index c5ff1b409cf..f45b8b7a2e1 100644 --- a/classes/i18n/PKPLocale.inc.php +++ b/classes/i18n/PKPLocale.inc.php @@ -15,1032 +15,90 @@ * @class PKPLocale * @ingroup i18n * - * @brief Provides methods for loading locale data and translating strings identified by unique keys + * @brief Deprecated class, kept only for backwards compatibility with external plugins */ namespace PKP\i18n; -use APP\i18n\AppLocale; -use Illuminate\Support\Facades\DB; -use PKP\cache\CacheManager; -use PKP\config\Config; -use PKP\core\Registry; -use PKP\db\DAORegistry; -use PKP\db\XMLDAO; -use PKP\facades\Repo; -use PKP\plugins\HookRegistry; -use PKP\plugins\PluginRegistry; +use PKP\facades\Locale; -if (!defined('LOCALE_DEFAULT')) { - define('LOCALE_DEFAULT', Config::getVar('i18n', 'locale')); -} -if (!defined('LOCALE_ENCODING')) { - define('LOCALE_ENCODING', Config::getVar('i18n', 'client_charset')); -} - -// Error types for locale checking. -// Note: Cannot use numeric symbols for the constants below because -// array_merge_recursive doesn't treat numeric keys nicely. -define('LOCALE_ERROR_MISSING_KEY', 'LOCALE_ERROR_MISSING_KEY'); -define('LOCALE_ERROR_EXTRA_KEY', 'LOCALE_ERROR_EXTRA_KEY'); -define('LOCALE_ERROR_DIFFERING_PARAMS', 'LOCALE_ERROR_DIFFERING_PARAMS'); -define('LOCALE_ERROR_MISSING_FILE', 'LOCALE_ERROR_MISSING_FILE'); - -define('EMAIL_ERROR_MISSING_EMAIL', 'EMAIL_ERROR_MISSING_EMAIL'); -define('EMAIL_ERROR_EXTRA_EMAIL', 'EMAIL_ERROR_EXTRA_EMAIL'); -define('EMAIL_ERROR_DIFFERING_PARAMS', 'EMAIL_ERROR_DIFFERING_PARAMS'); - -// Shared locale components -define('LOCALE_COMPONENT_PKP_COMMON', 0x00000001); -define('LOCALE_COMPONENT_PKP_ADMIN', 0x00000002); -define('LOCALE_COMPONENT_PKP_INSTALLER', 0x00000003); -define('LOCALE_COMPONENT_PKP_MANAGER', 0x00000004); -define('LOCALE_COMPONENT_PKP_READER', 0x00000005); -define('LOCALE_COMPONENT_PKP_SUBMISSION', 0x00000006); -define('LOCALE_COMPONENT_PKP_USER', 0x00000007); -define('LOCALE_COMPONENT_PKP_GRID', 0x00000008); -define('LOCALE_COMPONENT_PKP_DEFAULT', 0x00000009); -define('LOCALE_COMPONENT_PKP_EDITOR', 0x0000000A); -define('LOCALE_COMPONENT_PKP_REVIEWER', 0x0000000B); -define('LOCALE_COMPONENT_PKP_API', 0x0000000C); - -// Application-specific locale components -define('LOCALE_COMPONENT_APP_COMMON', 0x00000100); -define('LOCALE_COMPONENT_APP_MANAGER', 0x00000101); -define('LOCALE_COMPONENT_APP_SUBMISSION', 0x00000102); -define('LOCALE_COMPONENT_APP_AUTHOR', 0x00000103); -define('LOCALE_COMPONENT_APP_EDITOR', 0x00000104); -define('LOCALE_COMPONENT_APP_ADMIN', 0x00000105); -define('LOCALE_COMPONENT_APP_DEFAULT', 0x00000106); -define('LOCALE_COMPONENT_APP_API', 0x00000107); -define('LOCALE_COMPONENT_APP_EMAIL', 0x00000108); - -use PKP\session\SessionManager; - -// (Let PHPUnit tests define this first if necessary) -class PKPLocale -{ - public const MASTER_LOCALE = 'en_US'; - public const LOCALE_REGISTRY_FILE = 'registry/locales.xml'; - - public static $request; - - /** - * Get all supported UI locales for the current context. - * - * @return array - */ - public static function getSupportedLocales() - { - static $supportedLocales; - if (!isset($supportedLocales)) { - if (defined('SESSION_DISABLE_INIT')) { - $supportedLocales = AppLocale::getAllLocales(); - } elseif (($context = self::$request->getContext())) { - $supportedLocales = $context->getSupportedLocaleNames(); - } else { - $site = self::$request->getSite(); - $supportedLocales = $site->getSupportedLocaleNames(); - } - } - return $supportedLocales; - } - - /** - * Get all supported form locales for the current context. - * - * @return array - */ - public static function getSupportedFormLocales() - { - static $supportedFormLocales; - if (!isset($supportedFormLocales)) { - if (defined('SESSION_DISABLE_INIT')) { - $supportedFormLocales = AppLocale::getAllLocales(); - } elseif (($context = self::$request->getContext())) { - $supportedFormLocales = $context->getSupportedFormLocaleNames(); - } else { - $site = self::$request->getSite(); - $supportedFormLocales = $site->getSupportedLocaleNames(); - } - } - return $supportedFormLocales; - } - - /** - * Return the key name of the user's currently selected locale (default - * is "en_US" for U.S. English). - * - * @return string - */ - public static function getLocale() - { - static $currentLocale; - if (!isset($currentLocale)) { - if (defined('SESSION_DISABLE_INIT')) { - // If the locale is specified in the URL, allow - // it to override. (Necessary when locale is - // being set, as cookie will not yet be re-set) - $locale = AppLocale::$request->getUserVar('setLocale'); - if (empty($locale) || !in_array($locale, array_keys(AppLocale::getSupportedLocales()))) { - $locale = self::$request->getCookieVar('currentLocale'); - } - } else { - $sessionManager = SessionManager::getManager(); - $session = $sessionManager->getUserSession(); - $locale = self::$request->getUserVar('uiLocale'); - - $context = self::$request->getContext(); - $site = self::$request->getSite(); - - if (!isset($locale)) { - $locale = $session->getSessionVar('currentLocale'); - } - - if (!isset($locale)) { - $locale = self::$request->getCookieVar('currentLocale'); - } - - if (isset($locale)) { - // Check if user-specified locale is supported - if ($context != null) { - $locales = $context->getSupportedLocaleNames(); - } else { - $locales = $site->getSupportedLocaleNames(); - } - - if (!in_array($locale, array_keys($locales))) { - unset($locale); - } - } - - if (!isset($locale)) { - // Use context/site default - if ($context != null) { - $locale = $context->getPrimaryLocale(); - } - - if (!isset($locale)) { - $locale = $site->getPrimaryLocale(); - } - } - } - - if (!AppLocale::isLocaleValid($locale)) { - $locale = LOCALE_DEFAULT; - } - - $currentLocale = $locale; - } - return $currentLocale; - } - - /** - * Get the stack of "important" locales, most important first. - * - * @return array - */ - public static function getLocalePrecedence() - { - static $localePrecedence; - if (!isset($localePrecedence)) { - $localePrecedence = [AppLocale::getLocale()]; - - $context = self::$request->getContext(); - if ($context && !in_array($context->getPrimaryLocale(), $localePrecedence)) { - $localePrecedence[] = $context->getPrimaryLocale(); - } - - $site = self::$request->getSite(); - if ($site && !in_array($site->getPrimaryLocale(), $localePrecedence)) { - $localePrecedence[] = $site->getPrimaryLocale(); - } - } - return $localePrecedence; - } - - /** - * Retrieve the primary locale of the current context. - * - * @return string - */ - public static function getPrimaryLocale() - { - static $locale; - if ($locale) { - return $locale; - } - - if (defined('SESSION_DISABLE_INIT')) { - return $locale = LOCALE_DEFAULT; - } - - $context = self::$request->getContext(); - - if (isset($context)) { - $locale = $context->getPrimaryLocale(); - } - - if (!isset($locale)) { - $site = self::$request->getSite(); - $locale = $site->getPrimaryLocale(); - } - - if (!isset($locale) || !AppLocale::isLocaleValid($locale)) { - $locale = LOCALE_DEFAULT; - } - - return $locale; - } - - /** - * Get a list of locale files currently registered, either in all - * locales (in an array for each locale), or for a specific locale. - * - * @param string $locale Locale identifier (optional) - */ - public static function &getLocaleFiles($locale = null) - { - $localeFiles = & Registry::get('localeFiles', true, []); - if ($locale !== null) { - if (!isset($localeFiles[$locale])) { - $localeFiles[$locale] = []; - } - return $localeFiles[$locale]; - } - return $localeFiles; - } - - /** - * Add octothorpes to a key name for presentation of the key as missing. - * - * @param string $key - * - * @return string - */ - public static function addOctothorpes($key) - { - return '##' . htmlentities($key) . '##'; - } - - /** - * Translate a string using the selected locale. - * Substitution works by replacing tokens like "{$foo}" with the value - * of the parameter named "foo" (if supplied). - * - * @param string $key - * @param array $params named substitution parameters - * @param string $locale the locale to use - * @param callable $missingKeyHandler Callback to be invoked when a key cannot be found. - * - * @return string - */ - public static function translate($key, $params = [], $locale = null, $missingKeyHandler = [__CLASS__, 'addOctothorpes']) - { - if (!isset($locale)) { - $locale = AppLocale::getLocale(); - } - if (($key = trim($key)) == '') { - return ''; - } - - $localeFiles = & AppLocale::getLocaleFiles($locale); - $value = ''; - for ($i = count($localeFiles) - 1 ; $i >= 0 ; $i --) { - $value = $localeFiles[$i]->translate($key, $params); - if ($value !== null) { - return $value; - } - } - - // Add a missing key to the debug notes. - $notes = & Registry::get('system.debug.notes'); - $notes[] = ['debug.notes.missingLocaleKey', ['key' => $key]]; - - if (!HookRegistry::call('PKPLocale::translate', [&$key, &$params, &$locale, &$localeFiles, &$value])) { - // Add some octothorpes to missing keys to make them more obvious - return $missingKeyHandler($key); - } else { - return $value; - } - } - - /** - * Initialize the locale system. - * - * @param PKPRequest $request - */ - public static function initialize($request) - { - self::$request = $request; - - // Use defaults if locale info unspecified. - $locale = AppLocale::getLocale(); - setlocale(LC_ALL, $locale . '.' . LOCALE_ENCODING, $locale); - putenv("LC_ALL=${locale}"); - - AppLocale::registerLocaleFile($locale, "lib/pkp/locale/${locale}/common.po"); - - // Set site time zone - $timeZone = self::getTimeZone(); - date_default_timezone_set($timeZone); - - if (Config::getVar('general', 'installed')) { - // Set the time zone for DB - // Get the offset from UTC - $now = new \DateTime(); - $mins = $now->getOffset() / 60; - $sgn = ($mins < 0 ? -1 : 1); - $mins = abs($mins); - $hrs = floor($mins / 60); - $mins -= $hrs * 60; - $offset = sprintf('%+d:%02d', $hrs * $sgn, $mins); - - switch (Config::getVar('database', 'driver')) { - case 'mysql': - case 'mysqli': - DB::statement('SET time_zone = \'' . $offset . '\''); - break; - case 'postgres': - case 'postgres64': - case 'postgres7': - case 'postgres8': - case 'postgres9': - DB::statement('SET TIME ZONE INTERVAL \'' . $offset . '\' HOUR TO MINUTE'); - break; - default: assert(false); - } - } - } - - /** - * Build an associative array of LOCALE_COMPOMENT_... => filename - * (use getFilenameComponentMap instead) - * - * @param string $locale - * - * @return array - */ - public static function makeComponentMap($locale) - { - $baseDir = "lib/pkp/locale/${locale}/"; - - return [ - LOCALE_COMPONENT_PKP_COMMON => $baseDir . 'common.po', - LOCALE_COMPONENT_PKP_ADMIN => $baseDir . 'admin.po', - LOCALE_COMPONENT_PKP_INSTALLER => $baseDir . 'installer.po', - LOCALE_COMPONENT_PKP_MANAGER => $baseDir . 'manager.po', - LOCALE_COMPONENT_PKP_READER => $baseDir . 'reader.po', - LOCALE_COMPONENT_PKP_SUBMISSION => $baseDir . 'submission.po', - LOCALE_COMPONENT_PKP_EDITOR => $baseDir . 'editor.po', - LOCALE_COMPONENT_PKP_REVIEWER => $baseDir . 'reviewer.po', - LOCALE_COMPONENT_PKP_USER => $baseDir . 'user.po', - LOCALE_COMPONENT_PKP_GRID => $baseDir . 'grid.po', - LOCALE_COMPONENT_PKP_DEFAULT => $baseDir . 'default.po', - LOCALE_COMPONENT_PKP_API => $baseDir . 'api.po', - ]; - } - - /** - * Get an associative array of LOCALE_COMPOMENT_... => filename - * - * @param string $locale - * - * @return array - */ - public static function getFilenameComponentMap($locale) - { - $filenameComponentMap = & Registry::get('localeFilenameComponentMap', true, []); - if (!isset($filenameComponentMap[$locale])) { - $filenameComponentMap[$locale] = AppLocale::makeComponentMap($locale); - } - return $filenameComponentMap[$locale]; - } - - /** - * Load a set of locale components. Parameters of mixed length may - * be supplied, each a LOCALE_COMPONENT_... constant. An optional final - * parameter may be supplied to specify the locale (e.g. 'en_US'). - */ - public static function requireComponents() - { - $params = func_get_args(); - - $paramCount = count($params); - if ($paramCount === 0) { - return; - } - - // Get the locale - $lastParam = $params[$paramCount - 1]; - if (is_string($lastParam)) { - $locale = $lastParam; - $paramCount--; - } else { - $locale = AppLocale::getLocale(); - } - - // Backwards compatibility: the list used to be supplied - // as an array in the first parameter. - if (is_array($params[0])) { - $params = $params[0]; - $paramCount = count($params); - } - - // Go through and make sure each component is loaded if valid. - $loadedComponents = & Registry::get('loadedLocaleComponents', true, []); - $filenameComponentMap = AppLocale::getFilenameComponentMap($locale); - for ($i = 0; $i < $paramCount; $i++) { - $component = $params[$i]; - - // Don't load components twice - if (isset($loadedComponents[$locale][$component])) { - continue; - } - - // Validate component - if (!isset($filenameComponentMap[$component])) { - fatalError('Unknown locale component ' . $component); - } - - $filename = $filenameComponentMap[$component]; - AppLocale::registerLocaleFile($locale, $filename); - $loadedComponents[$locale][$component] = true; - } - } - - /** - * Register a locale file against the current list. - * - * @param string $locale Locale key - * @param string $filename Filename to new locale XML file - * @param bool $addToTop Whether to add to the top of the list (true) - * or the bottom (false). Allows overriding. - */ - public static function registerLocaleFile($locale, $filename, $addToTop = false) - { - $localeFiles = & AppLocale::getLocaleFiles($locale); - $localeFile = new LocaleFile($locale, $filename); - - if (!HookRegistry::call('PKPLocale::registerLocaleFile::isValidLocaleFile', [&$localeFile])) { - if (!$localeFile->isValid()) { - return null; - } - } - if ($addToTop) { - // Work-around: unshift by reference. - array_unshift($localeFiles, ''); - $localeFiles[0] = & $localeFile; - } else { - $localeFiles[] = & $localeFile; - } - HookRegistry::call('PKPLocale::registerLocaleFile', [&$locale, &$filename, &$addToTop]); - return $localeFile; - } - - /** - * Get the stylesheet filename for a particular locale. - * - * @param string $locale - * - * @return string or null if none configured. - */ - public static function getLocaleStyleSheet($locale) - { - $contents = & AppLocale::_getAllLocalesCacheContent(); - if (isset($contents[$locale]['stylesheet'])) { - return $contents[$locale]['stylesheet']; - } - return null; - } - - /** - * Get the reading direction for a particular locale. - * - * A locale can specify a reading direction with the `direction` attribute. If no - * direction is specified, defaults to `ltr` (left-to-right). The only - * other value that is expected is `rtl`. This value is used in HTML and - * CSS markup to present a right-to-left layout. - * - * @param string $locale - * - * @return string - */ - public static function getLocaleDirection($locale) - { - $contents = & AppLocale::_getAllLocalesCacheContent(); - if (isset($contents[$locale]['direction'])) { - return $contents[$locale]['direction']; - } - return 'ltr'; - } - - /** - * Determine whether or not a locale is marked incomplete. - * - * @param string $locale xx_XX symbolic name of locale to check - * - * @return bool - */ - public static function isLocaleComplete($locale) - { - $contents = & AppLocale::_getAllLocalesCacheContent(); - if (!isset($contents[$locale])) { - return false; - } - if (isset($contents[$locale]['complete']) && $contents[$locale]['complete'] == 'false') { - return false; - } - return true; - } - - /** - * Determine whether or not a locale uses family name first. - * - * @param string $locale xx_XX symbolic name of locale to check - * - * @return bool - */ - public static function isLocaleWithFamilyFirst($locale) - { - $contents = & AppLocale::_getAllLocalesCacheContent(); - if (isset($contents[$locale]) && isset($contents[$locale]['familyFirst']) && $contents[$locale]['familyFirst'] == 'true') { - return true; - } - return false; - } - - /** - * Check if the supplied locale is currently installable. - * - * @param string $locale - * - * @return bool - */ - public static function isLocaleValid($locale) - { - if (empty($locale)) { - return false; - } - // variants can be composed of five to eight letters, or of four characters starting with a digit - if (!preg_match('/^[a-z][a-z]_[A-Z][A-Z](@([A-Za-z0-9]{5,8}|\d[A-Za-z0-9]{3}))?$/', $locale)) { - return false; - } - if (file_exists('locale/' . $locale)) { - return true; - } - return false; - } - - /** - * Load a locale list from a file. - * - * @param string $filename - * - * @return array - */ - public static function &loadLocaleList($filename) - { - $xmlDao = new XMLDAO(); - $data = $xmlDao->parseStruct($filename, ['locale']); - $allLocales = []; - - // Build array with ($localKey => $localeName) - if (isset($data['locale'])) { - foreach ($data['locale'] as $localeData) { - $allLocales[$localeData['attributes']['key']] = $localeData['attributes']; - } - } - - return $allLocales; - } - - /** - * Return a list of all available locales. - * - * @return array - */ - public static function &getAllLocales() - { - $rawContents = & AppLocale::_getAllLocalesCacheContent(); - $allLocales = []; - - foreach ($rawContents as $locale => $contents) { - $allLocales[$locale] = $contents['name']; - } - - // if client encoding is set to iso-8859-1, transcode locales from utf8 - if (LOCALE_ENCODING == 'iso-8859-1') { - $allLocales = array_map('utf8_decode', $allLocales); - } - - return $allLocales; - } - - /** - * Install support for a new locale. - * - * @param string $locale - */ - public static function installLocale($locale) - { - // Install default locale-specific data - AppLocale::requireComponents(LOCALE_COMPONENT_APP_EMAIL, $locale); - Repo::emailTemplate()->dao->installEmailTemplateLocaleData(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), [$locale]); - - // Load all plugins so they can add locale data if needed - $categories = PluginRegistry::getCategories(); - foreach ($categories as $category) { - PluginRegistry::loadCategory($category); - } - HookRegistry::call('PKPLocale::installLocale', [&$locale]); - } - - /** - * Uninstall support for an existing locale. - * - * @param string $locale - */ - public static function uninstallLocale($locale) - { - // Delete locale-specific data - Repo::emailTemplate()->dao->deleteEmailTemplatesByLocale($locale); - Repo::emailTemplate()->dao->deleteDefaultEmailTemplatesByLocale($locale); - } - - /** - * Reload locale-specific data. - * - * @param string $locale - */ - public static function reloadLocale($locale) - { - AppLocale::installLocale($locale); - } - - /** - * Given a locale string, get the list of parameter references of the - * form {$myParameterName}. - * - * @param string $source - * - * @return array - */ - public static function getParameterNames($source) - { - $matches = null; - PKPString::regexp_match_all('/({\$[^}]+})/' /* '/{\$[^}]+})/' */, $source, $matches); - array_shift($matches); // Knock the top element off the array - if (isset($matches[0])) { - return $matches[0]; - } - return []; - } - - /** - * Translate the ISO 2-letter language string (ISO639-1) - * into a ISO compatible 3-letter string (ISO639-2b). - * - * @param string $iso2Letter - * - * @return string the translated string or null if we - * don't know about the given language. - */ - public static function get3LetterFrom2LetterIsoLanguage($iso2Letter) - { - assert(strlen($iso2Letter) == 2); - $locales = & AppLocale::_getAllLocalesCacheContent(); - foreach ($locales as $locale => $localeData) { - if (substr($locale, 0, 2) == $iso2Letter) { - assert(isset($localeData['iso639-2b'])); - return $localeData['iso639-2b']; - } - } - return null; - } - - /** - * Translate the ISO 3-letter language string (ISO639-2b) - * into a ISO compatible 2-letter string (ISO639-1). - * - * @param string $iso3Letter - * - * @return string the translated string or null if we - * don't know about the given language. - */ - public static function get2LetterFrom3LetterIsoLanguage($iso3Letter) - { - assert(strlen($iso3Letter) == 3); - $locales = & AppLocale::_getAllLocalesCacheContent(); - foreach ($locales as $locale => $localeData) { - assert(isset($localeData['iso639-2b'])); - if ($localeData['iso639-2b'] == $iso3Letter) { - return substr($locale, 0, 2); - } - } - return null; - } - - /** - * Translate the PKP locale identifier into an - * ISO639-2b compatible 3-letter string. - * - * @param string $locale - * - * @return string - */ - public static function get3LetterIsoFromLocale($locale) - { - assert(strlen($locale) >= 5); - $iso2Letter = substr($locale, 0, 2); - return AppLocale::get3LetterFrom2LetterIsoLanguage($iso2Letter); - } - - /** - * Translate an ISO639-2b compatible 3-letter string - * into the PKP locale identifier. - * - * This can be ambiguous if several locales are defined - * for the same language. In this case we'll use the - * primary locale to disambiguate. - * - * If that still doesn't determine a unique locale then - * we'll choose the first locale found. - * - * @return string - */ - public static function getLocaleFrom3LetterIso($iso3Letter) - { - assert(strlen($iso3Letter) == 3); - $primaryLocale = AppLocale::getPrimaryLocale(); - - $localeCandidates = []; - $locales = & AppLocale::_getAllLocalesCacheContent(); - foreach ($locales as $locale => $localeData) { - assert(isset($localeData['iso639-2b'])); - if ($localeData['iso639-2b'] == $iso3Letter) { - if ($locale == $primaryLocale) { - // In case of ambiguity the primary locale - // overrides all other options so we're done. - return $primaryLocale; - } - $localeCandidates[] = $locale; - } - } - - // Return null if we found no candidate locale. - if (empty($localeCandidates)) { - return null; - } - - if (count($localeCandidates) > 1) { - // Check whether one of the candidate locales - // is a supported locale. If so choose the first - // supported locale. - $supportedLocales = AppLocale::getSupportedLocales(); - foreach ($supportedLocales as $supportedLocale => $localeName) { - if (in_array($supportedLocale, $localeCandidates)) { - return $supportedLocale; - } - } - } - - // If there is only one candidate (or if we were - // unable to disambiguate) then return the unique - // (first) candidate found. - return array_shift($localeCandidates); - } - - /** - * Translate the ISO 2-letter language string (ISO639-1) into ISO639-3. - * - * @param string $iso1 - * - * @return string the translated string or null if we - * don't know about the given language. - */ - public static function getIso3FromIso1($iso1) - { - assert(strlen($iso1) == 2); - $locales = & AppLocale::_getAllLocalesCacheContent(); - foreach ($locales as $locale => $localeData) { - if (substr($locale, 0, 2) == $iso1) { - assert(isset($localeData['iso639-3'])); - return $localeData['iso639-3']; - } - } - return null; - } - - /** - * Translate the ISO639-3 into ISO639-1. - * - * @param string $iso3 - * - * @return string the translated string or null if we - * don't know about the given language. - */ - public static function getIso1FromIso3($iso3) - { - assert(strlen($iso3) == 3); - $locales = & AppLocale::_getAllLocalesCacheContent(); - foreach ($locales as $locale => $localeData) { - assert(isset($localeData['iso639-3'])); - if ($localeData['iso639-3'] == $iso3) { - return substr($locale, 0, 2); - } - } - return null; - } - - /** - * Translate the PKP locale identifier into an - * ISO639-3 compatible 3-letter string. - * - * @param string $locale - * - * @return string - */ - public static function getIso3FromLocale($locale) - { - assert(strlen($locale) >= 5); - $iso1 = substr($locale, 0, 2); - return AppLocale::getIso3FromIso1($iso1); - } - - /** - * Translate the PKP locale identifier into an - * ISO639-1 compatible 2-letter string. - * - * @param string $locale - * - * @return string - */ - public static function getIso1FromLocale($locale) - { - assert(strlen($locale) >= 5); - return substr($locale, 0, 2); - } - - /** - * Translate an ISO639-3 compatible 3-letter string - * into the PKP locale identifier. - * - * This can be ambiguous if several locales are defined - * for the same language. In this case we'll use the - * primary locale to disambiguate. - * - * If that still doesn't determine a unique locale then - * we'll choose the first locale found. - * - * @param string $iso3 - * - * @return string - */ - public static function getLocaleFromIso3($iso3) - { - assert(strlen($iso3) == 3); - $primaryLocale = AppLocale::getPrimaryLocale(); - - $localeCandidates = []; - $locales = & AppLocale::_getAllLocalesCacheContent(); - foreach ($locales as $locale => $localeData) { - assert(isset($localeData['iso639-3'])); - if ($localeData['iso639-3'] == $iso3) { - if ($locale == $primaryLocale) { - // In case of ambiguity the primary locale - // overrides all other options so we're done. - return $primaryLocale; - } - $localeCandidates[] = $locale; - } - } - - // Return null if we found no candidate locale. - if (empty($localeCandidates)) { - return null; - } - - if (count($localeCandidates) > 1) { - // Check whether one of the candidate locales - // is a supported locale. If so choose the first - // supported locale. - $supportedLocales = AppLocale::getSupportedLocales(); - foreach ($supportedLocales as $supportedLocale => $localeName) { - if (in_array($supportedLocale, $localeCandidates)) { - return $supportedLocale; - } - } - } - - // If there is only one candidate (or if we were - // unable to disambiguate) then return the unique - // (first) candidate found. - return array_shift($localeCandidates); - } - - // - // Private helper methods. - // - /** - * Retrieves locale data from the locales cache. - * - * @return array - */ - public static function &_getAllLocalesCacheContent() - { - static $contents = false; - if ($contents === false) { - $allLocalesCache = & AppLocale::_getAllLocalesCache(); - $contents = $allLocalesCache->getContents(); - } - return $contents; - } - - /** - * Get the cache object for the current list of all locales. - * - * @return FileCache - */ - public static function &_getAllLocalesCache() - { - $cache = & Registry::get('allLocalesCache', true, null); - if ($cache === null) { - $cacheManager = CacheManager::getManager(); - $cache = $cacheManager->getFileCache( - 'locale', - 'list', - ['\APP\i18n\AppLocale', '_allLocalesCacheMiss'] - ); - - // Check to see if the data is outdated - $cacheTime = $cache->getCacheTime(); - if ($cacheTime !== null && $cacheTime < filemtime(AppLocale::LOCALE_REGISTRY_FILE)) { - $cache->flush(); - } - } - return $cache; - } +if (!PKP_STRICT_MODE) { /** - * Create a cache file with locale data. - * - * @param CacheManager $cache - * @param string $id the cache id (not used here, required by the cache manager) + * @deprecated The class \PKP\i18n\PKPLocale has been replaced by PKP\facades\Locale */ - public static function _allLocalesCacheMiss($cache, $id) + class PKPLocale { - $allLocales = & Registry::get('allLocales', true, null); - if ($allLocales === null) { - // Add a locale load to the debug notes. - $notes = & Registry::get('system.debug.notes'); - $notes[] = ['debug.notes.localeListLoad', ['localeList' => AppLocale::LOCALE_REGISTRY_FILE]]; - - // Reload locale registry file - $allLocales = AppLocale::loadLocaleList(AppLocale::LOCALE_REGISTRY_FILE); - asort($allLocales); - $cache->setEntireCache($allLocales); + /** + * Return the key name of the user's currently selected locale (default + * is "en_US" for U.S. English). + * + * @return string + * @deprecated 3.4.0.0 The same method is available at \PKP\facades\Locale::getLocale() + */ + public static function getLocale() + { + return Locale::getLocale(); } - return null; - } - - /** - * Get the sites time zone. - * - * @return string Time zone - */ - public static function getTimeZone() - { - $timeZone = null; - // Load the time zone from the configuration file - if ($timeZoneConfig = Config::getVar('general', 'time_zone')) { - $timeZoneDAO = DAORegistry::getDAO('TimeZoneDAO'); - $timeZoneList = $timeZoneDAO->getTimeZones(); - foreach ($timeZoneList as $timeZoneKey => $timeZoneName) { - if (in_array($timeZoneConfig, [$timeZoneKey, $timeZoneName])) { - $timeZone = $timeZoneKey; - break; - } - } + /** + * Retrieve the primary locale of the current context. + * + * @return string + * @deprecated 3.4.0.0 The same method is available at \PKP\facades\Locale::getPrimaryLocale(), but before using this method, try to retrieve the locale directly from a nearby context + */ + public static function getPrimaryLocale() + { + return Locale::getPrimaryLocale(); } - // Fall back to the time zone set in php.ini - if (empty($timeZone)) { - $timeZone = ini_get('date.timezone'); + /** + * Load a set of locale components. Parameters of mixed length may + * be supplied, each a LOCALE_COMPONENT_... constant. An optional final + * parameter may be supplied to specify the locale (e.g. 'en_US'). + * @deprecated 3.4.0.0 All the available locale keys are already loaded + */ + public static function requireComponents() + { } - // Fall back to UTC - if (empty($timeZone)) { - $timeZone = 'UTC'; + /** + * Return a list of all available locales. + * + * @deprecated 3.4.0.0 Use the \PKP\facades\Locale::getLocales() + * @return array + */ + public static function &getAllLocales() + { + $locales = array_map(fn (LocaleMetadata $locale) => $locale->getDisplayName(), Locale::getLocales()); + return $locales; } - - return $timeZone; } -} -if (!PKP_STRICT_MODE) { class_alias('\PKP\i18n\PKPLocale', '\PKPLocale'); - // REGISTRY_LOCALE_FILE excluded because of PHPUnit interaction. - define('MASTER_LOCALE', PKPLocale::MASTER_LOCALE); + + // Shared locale components + define('LOCALE_COMPONENT_PKP_COMMON', 0x00000001); + define('LOCALE_COMPONENT_PKP_ADMIN', 0x00000002); + define('LOCALE_COMPONENT_PKP_INSTALLER', 0x00000003); + define('LOCALE_COMPONENT_PKP_MANAGER', 0x00000004); + define('LOCALE_COMPONENT_PKP_READER', 0x00000005); + define('LOCALE_COMPONENT_PKP_SUBMISSION', 0x00000006); + define('LOCALE_COMPONENT_PKP_USER', 0x00000007); + define('LOCALE_COMPONENT_PKP_GRID', 0x00000008); + define('LOCALE_COMPONENT_PKP_DEFAULT', 0x00000009); + define('LOCALE_COMPONENT_PKP_EDITOR', 0x0000000A); + define('LOCALE_COMPONENT_PKP_REVIEWER', 0x0000000B); + define('LOCALE_COMPONENT_PKP_API', 0x0000000C); + + // Application-specific locale components + define('LOCALE_COMPONENT_APP_COMMON', 0x00000100); + define('LOCALE_COMPONENT_APP_MANAGER', 0x00000101); + define('LOCALE_COMPONENT_APP_SUBMISSION', 0x00000102); + define('LOCALE_COMPONENT_APP_AUTHOR', 0x00000103); + define('LOCALE_COMPONENT_APP_EDITOR', 0x00000104); + define('LOCALE_COMPONENT_APP_ADMIN', 0x00000105); + define('LOCALE_COMPONENT_APP_DEFAULT', 0x00000106); + define('LOCALE_COMPONENT_APP_API', 0x00000107); + define('LOCALE_COMPONENT_APP_EMAIL', 0x00000108); } diff --git a/classes/i18n/TimeZoneDAO.inc.php b/classes/i18n/TimeZoneDAO.inc.php deleted file mode 100644 index 68fc3d09e21..00000000000 --- a/classes/i18n/TimeZoneDAO.inc.php +++ /dev/null @@ -1,98 +0,0 @@ -getFileCache( - 'timeZone', - 'list', - [$this, '_timeZoneCacheMiss'] - ); - - // Check to see if the data is outdated - $cacheTime = $cache->getCacheTime(); - if ($cacheTime !== null && $cacheTime < filemtime($this->getFilename())) { - $cache->flush(); - } - } - return $cache; - } - - public function _timeZoneCacheMiss($cache, $id) - { - $timeZones = & Registry::get('allTimeZonesData', true, null); - if ($timeZones === null) { - // Reload time zone registry file - $xmlDao = new XMLDAO(); - $data = $xmlDao->parseStruct($this->getFilename(), ['timezones', 'entry']); - $timeZones = []; - if (isset($data['timezones'])) { - foreach ($data['entry'] as $timeZoneData) { - $timeZones[$timeZoneData['attributes']['key']] = $timeZoneData['attributes']['name']; - } - } - asort($timeZones); - $cache->setEntireCache($timeZones); - } - return null; - } - - /** - * Return a list of all time zones. - * - * @return array - */ - public function &getTimeZones() - { - $cache = & $this->_getTimeZoneCache(); - return $cache->getContents(); - } -} - -if (!PKP_STRICT_MODE) { - class_alias('\PKP\i18n\TimeZoneDAO', '\TimeZoneDAO'); -} diff --git a/classes/i18n/interfaces/LocaleInterface.inc.php b/classes/i18n/interfaces/LocaleInterface.inc.php new file mode 100644 index 00000000000..0200f70c65f --- /dev/null +++ b/classes/i18n/interfaces/LocaleInterface.inc.php @@ -0,0 +1,151 @@ +[a-z]{2})(?:_(?P[A-Z]{2}))?(?:@(?P
@@ -79,7 +87,7 @@ {fbvElement type="text" password=true id="adminPassword" value=$adminPassword maxlength="32" size=$fbvStyles.size.MEDIUM} {/fbvFormSection} {fbvFormSection label="user.repeatPassword"} - {fbvElement type="text" password=true id="adminPassword2" value=$adminPassword2|default:""|escape maxlength="32" size=$fbvStyles.size.MEDIUM} + {fbvElement type="text" password=true id="adminPassword2" value=$adminPassword2 maxlength="32" size=$fbvStyles.size.MEDIUM} {/fbvFormSection} {fbvFormSection label="user.email"} {fbvElement type="text" id="adminEmail" value=$adminEmail maxlength="90" size=$fbvStyles.size.MEDIUM} @@ -106,13 +114,8 @@ {fbvElement type="checkbox" name="additionalLocales[]" id="additionalLocales-$localeKeyEscaped" value=$localeKeyEscaped translate=false label="manager.people.createUserSendNotify" checked=$localeSelected label=$localeName|escape} {/foreach} {/fbvFormSection} - - {fbvFormSection label="installer.clientCharset" description="installer.clientCharsetInstructions"} - {fbvElement type="select" id="clientCharset" from=$clientCharsetOptions selected=$clientCharset translate=false size=$fbvStyles.size.SMALL} - {/fbvFormSection} - - {fbvFormSection label="installer.connectionCharset"} - {fbvElement type="select" id="connectionCharset" from=$connectionCharsetOptions selected=$connectionCharset translate=false size=$fbvStyles.size.SMALL} + {fbvFormSection label="timeZone" description="installer.timezoneInstructions" for="timeZone"} + {fbvElement type="select" name="timeZone" id="timeZoneOptions" from=$timeZoneOptions selected=$timeZone translate=false size=$fbvStyles.size.SMALL subLabelTranslate=true} {/fbvFormSection} {/fbvFormArea} @@ -140,7 +143,7 @@ {fbvElement type="text" id="databaseUsername" value=$databaseUsername maxlength="60" size=$fbvStyles.size.MEDIUM} {/fbvFormSection} {fbvFormSection label="installer.databasePassword"} - {fbvElement type="text" id="databasePassword" value=$databasePassword maxlength="60" size=$fbvStyles.size.MEDIUM} + {fbvElement type="text" password=true id="databasePassword" value=$databasePassword maxlength="60" size=$fbvStyles.size.MEDIUM} {/fbvFormSection} {fbvFormSection label="installer.databaseName"} {fbvElement type="text" id="databaseName" value=$databaseName maxlength="60" size=$fbvStyles.size.MEDIUM} diff --git a/tests/classes/core/PKPComponentRouterTest.php b/tests/classes/core/PKPComponentRouterTest.php index 0510763374d..7a2bf956947 100644 --- a/tests/classes/core/PKPComponentRouterTest.php +++ b/tests/classes/core/PKPComponentRouterTest.php @@ -18,7 +18,7 @@ require_mock_env('env1'); import('classes.core.Request'); // This will import our mock request class. -import('classes.i18n.AppLocale'); // This will import our mock AppLocale class. +import('classes.i18n.Locale'); // This will import our mock Locale class. import('lib.pkp.tests.classes.core.PKPRouterTestCase'); use PKP\core\PKPComponentRouter; diff --git a/tests/classes/core/PKPPageRouterTest.php b/tests/classes/core/PKPPageRouterTest.php index dd952370461..10d405ce775 100644 --- a/tests/classes/core/PKPPageRouterTest.php +++ b/tests/classes/core/PKPPageRouterTest.php @@ -22,7 +22,7 @@ import('classes.core.Request'); // This will import our mock router class. import('lib.pkp.tests.classes.core.PKPRouterTestCase'); import('classes.security.Validation'); // This will import our mock validation class. -import('classes.i18n.AppLocale'); // This will import our mock locale. +import('classes.i18n.Locale'); // This will import our mock locale. use PKP\security\Validation; diff --git a/tests/classes/form/validation/FormValidatorLocaleTest.php b/tests/classes/form/validation/FormValidatorLocaleTest.php index d4e085957c4..92e622647ad 100644 --- a/tests/classes/form/validation/FormValidatorLocaleTest.php +++ b/tests/classes/form/validation/FormValidatorLocaleTest.php @@ -32,7 +32,7 @@ public function testGetMessage() { $form = new Form('some template'); $formValidator = new \PKP\form\validation\FormValidatorLocale($form, 'testData', FormValidator::FORM_VALIDATOR_REQUIRED_VALUE, 'some.message.key'); - self::assertSame('##some.message.key## (English)', $formValidator->getMessage()); + self::assertSame('##some.message.key## (English (United States))', $formValidator->getMessage()); } /** diff --git a/tests/classes/i18n/LocaleTest.php b/tests/classes/i18n/LocaleTest.php new file mode 100644 index 00000000000..c2b165d359d --- /dev/null +++ b/tests/classes/i18n/LocaleTest.php @@ -0,0 +1,117 @@ +isComplete()); + self::assertFalse(Locale::getMetadata('pt_BR')->isComplete()); + self::assertNull(Locale::getMetadata('xx_XX')); + } + + /** + * @covers Locale + */ + public function testGetLocales() + { + $expectedLocales = [ + 'en_US' => 'English (United States)', + 'pt_BR' => 'Portuguese (Brazil)', + 'pt_PT' => 'Portuguese (Portugal)', + 'de_DE' => 'German (Germany)' + ]; + $locales = array_map(fn(LocaleMetadata $locale) => $locale->getDisplayName(), Locale::getLocales()); + self::assertEquals($expectedLocales, $locales); + } + + /** + * @covers Locale + */ + public function testGet3LetterFrom2LetterIsoLanguage() + { + self::assertEquals('eng', LocaleConversion::get3LetterFrom2LetterIsoLanguage('en')); + self::assertEquals('por', LocaleConversion::get3LetterFrom2LetterIsoLanguage('pt')); + self::assertNull(LocaleConversion::get3LetterFrom2LetterIsoLanguage('xx')); + } + + /** + * @covers Locale + */ + public function testGet2LetterFrom3LetterIsoLanguage() + { + self::assertEquals('en', LocaleConversion::get2LetterFrom3LetterIsoLanguage('eng')); + self::assertEquals('pt', LocaleConversion::get2LetterFrom3LetterIsoLanguage('por')); + self::assertNull(LocaleConversion::get2LetterFrom3LetterIsoLanguage('xxx')); + } + + /** + * @covers Locale + */ + public function testGet3LetterIsoFromLocale() + { + self::assertEquals('eng', LocaleConversion::get3LetterIsoFromLocale('en_US')); + self::assertEquals('por', LocaleConversion::get3LetterIsoFromLocale('pt_BR')); + self::assertEquals('por', LocaleConversion::get3LetterIsoFromLocale('pt_PT')); + self::assertNull(LocaleConversion::get3LetterIsoFromLocale('xx_XX')); + } + + /** + * @covers Locale + */ + public function testGetLocaleFrom3LetterIso() + { + // A locale that does not have to be disambiguated. + self::assertEquals('en_US', LocaleConversion::getLocaleFrom3LetterIso('eng')); + + // The primary locale will be used if that helps to disambiguate. + Locale::setSupportedLocales(['en_US' => 'English', 'pt_BR' => 'Portuguese (Brazil)', 'pt_PT' => 'Portuguese (Portugal)']); + Locale::setPrimaryLocale('pt_BR'); + self::assertEquals('pt_BR', LocaleConversion::getLocaleFrom3LetterIso('por')); + Locale::setPrimaryLocale('pt_PT'); + self::assertEquals('pt_PT', LocaleConversion::getLocaleFrom3LetterIso('por')); + + // If the primary locale doesn't help then use the first supported locale found. + Locale::setPrimaryLocale('en_US'); + self::assertEquals('pt_BR', LocaleConversion::getLocaleFrom3LetterIso('por')); + Locale::setSupportedLocales(['en_US' => 'English', 'pt_PT' => 'Portuguese (Portugal)', 'pt_BR' => 'Portuguese (Brazil)']); + self::assertEquals('pt_PT', LocaleConversion::getLocaleFrom3LetterIso('por')); + + // If the locale isn't even in the supported locales then use the first locale found. + Locale::setSupportedLocales(['en_US' => 'English']); + self::assertEquals('pt_BR', LocaleConversion::getLocaleFrom3LetterIso('por')); + + // Unknown language. + self::assertNull(LocaleConversion::getLocaleFrom3LetterIso('xxx')); + } +} diff --git a/tests/classes/i18n/PKPLocaleTest.php b/tests/classes/i18n/PKPLocaleTest.php deleted file mode 100644 index d4702c14cfb..00000000000 --- a/tests/classes/i18n/PKPLocaleTest.php +++ /dev/null @@ -1,126 +0,0 @@ - 'English', - 'pt_BR' => 'Portuguese (Brazil)', - 'pt_PT' => 'Portuguese (Portugal)', - 'de_DE' => 'German' - ]; - self::assertEquals($expectedLocales, AppLocale::getAllLocales()); - } - - /** - * @covers PKPLocale - */ - public function testGet3LetterFrom2LetterIsoLanguage() - { - self::assertEquals('eng', AppLocale::get3LetterFrom2LetterIsoLanguage('en')); - self::assertEquals('por', AppLocale::get3LetterFrom2LetterIsoLanguage('pt')); - self::assertNull(AppLocale::get3LetterFrom2LetterIsoLanguage('xx')); - } - - /** - * @covers PKPLocale - */ - public function testGet2LetterFrom3LetterIsoLanguage() - { - self::assertEquals('en', AppLocale::get2LetterFrom3LetterIsoLanguage('eng')); - self::assertEquals('pt', AppLocale::get2LetterFrom3LetterIsoLanguage('por')); - self::assertNull(AppLocale::get2LetterFrom3LetterIsoLanguage('xxx')); - } - - /** - * @covers PKPLocale - */ - public function testGet3LetterIsoFromLocale() - { - self::assertEquals('eng', AppLocale::get3LetterIsoFromLocale('en_US')); - self::assertEquals('por', AppLocale::get3LetterIsoFromLocale('pt_BR')); - self::assertEquals('por', AppLocale::get3LetterIsoFromLocale('pt_PT')); - self::assertNull(AppLocale::get3LetterIsoFromLocale('xx_XX')); - } - - /** - * @covers PKPLocale - */ - public function testGetLocaleFrom3LetterIso() - { - // A locale that does not have to be disambiguated. - self::assertEquals('en_US', AppLocale::getLocaleFrom3LetterIso('eng')); - - // The primary locale will be used if that helps - // to disambiguate. - AppLocale::setSupportedLocales(['en_US' => 'English', 'pt_BR' => 'Portuguese (Brazil)', 'pt_PT' => 'Portuguese (Portugal)']); - AppLocale::setPrimaryLocale('pt_BR'); - self::assertEquals('pt_BR', AppLocale::getLocaleFrom3LetterIso('por')); - AppLocale::setPrimaryLocale('pt_PT'); - self::assertEquals('pt_PT', AppLocale::getLocaleFrom3LetterIso('por')); - - // If the primary locale doesn't help then use the first supported locale found. - AppLocale::setPrimaryLocale('en_US'); - self::assertEquals('pt_BR', AppLocale::getLocaleFrom3LetterIso('por')); - AppLocale::setSupportedLocales(['en_US' => 'English', 'pt_PT' => 'Portuguese (Portugal)', 'pt_BR' => 'Portuguese (Brazil)']); - self::assertEquals('pt_PT', AppLocale::getLocaleFrom3LetterIso('por')); - - // If the locale isn't even in the supported localse then use the first locale found. - AppLocale::setSupportedLocales(['en_US' => 'English']); - self::assertEquals('pt_PT', AppLocale::getLocaleFrom3LetterIso('por')); - - // Unknown language. - self::assertNull(AppLocale::getLocaleFrom3LetterIso('xxx')); - } -} diff --git a/tests/classes/notification/PKPNotificationManagerTest.php b/tests/classes/notification/PKPNotificationManagerTest.php index 5fef704380f..04b9a26b3b0 100644 --- a/tests/classes/notification/PKPNotificationManagerTest.php +++ b/tests/classes/notification/PKPNotificationManagerTest.php @@ -18,12 +18,18 @@ import('lib.pkp.tests.PKPTestCase'); +use APP\core\Application; use APP\notification\Notification; use Illuminate\Support\Facades\App; +use PKP\core\PKPRequest; +use PKP\core\Registry; use PKP\db\DAORegistry; use PKP\mail\MailTemplate; +use PKP\notification\NotificationDAO; +use PKP\notification\NotificationSettingsDAO; use PKP\notification\PKPNotification; use PKP\notification\PKPNotificationManager; +use PKP\site\Site; use PKP\user\User; define('NOTIFICATION_ID', 1); @@ -309,7 +315,7 @@ private function getFixtureCreateNotificationSendEmail($expectedNotification) // Stub site. $siteStub = $this->getMockBuilder(Site::class) - ->setMethods(['getLocalizedContactName', 'getLocalizedTitle', 'getLocalizedContactEmail']) + ->setMethods(['getLocalizedContactName', 'getLocalizedTitle', 'getLocalizedContactEmail', 'getPrimaryLocale']) ->getMock(); $siteStub->expects($this->any()) @@ -321,7 +327,9 @@ private function getFixtureCreateNotificationSendEmail($expectedNotification) $siteStub->expects($this->any()) ->method('getLocalizedContactEmail') ->will($this->returnValue($siteEmail)); - + $siteStub->expects($this->any()) + ->method('getPrimaryLocale') + ->will($this->returnValue('en_US')); // Inject site stub into our request stub. $requestStub->expects($this->any()) ->method('getSite') diff --git a/tests/config/config.TEMPLATE.mysql.inc.php b/tests/config/config.TEMPLATE.mysql.inc.php index 2c9b4ea2b59..4d1a6faf6f0 100644 --- a/tests/config/config.TEMPLATE.mysql.inc.php +++ b/tests/config/config.TEMPLATE.mysql.inc.php @@ -35,7 +35,6 @@ [i18n] locale = en_US -client_charset = utf-8 connection_charset = utf8 [files] diff --git a/tests/config/config.TEMPLATE.pgsql.inc.php b/tests/config/config.TEMPLATE.pgsql.inc.php index 3badacae832..8d00fb6179b 100644 --- a/tests/config/config.TEMPLATE.pgsql.inc.php +++ b/tests/config/config.TEMPLATE.pgsql.inc.php @@ -35,7 +35,6 @@ [i18n] locale = en_US -client_charset = utf-8 connection_charset = utf8 [files] diff --git a/tests/mock/env1/MockAppLocale.inc.php b/tests/mock/env1/MockAppLocale.inc.php deleted file mode 100755 index 19113e9923d..00000000000 --- a/tests/mock/env1/MockAppLocale.inc.php +++ /dev/null @@ -1,163 +0,0 @@ - 'English/America']; - public static $translations = []; - - /** - * method required during setup of - * the PKP application framework - */ - public static function initialize($request) - { - // do nothing - } - - /** - * method required during setup of - * the PKP application framework - * @return string test locale - */ - public static function getLocale() - { - return 'en_US'; - } - - /** - * method required during setup of - * the PKP application framework - */ - public static function registerLocaleFile($locale, $filename, $addToTop = false) - { - // do nothing - } - - /** - * method required during setup of - * the PKP templating engine and application framework - */ - public static function requireComponents() - { - // do nothing - } - - /** - * Mocked method - * - * @return array a test array of locales - */ - public static function getLocalePrecedence() - { - return ['en_US', 'fr_FR']; - } - - /** - * Mocked method - * - * @param string $key - * @param array $params named substitution parameters - * @param string $locale the locale to use - * - * @return string - */ - public static function translate($key, $params = [], $locale = null, $missingKeyHandler = []) - { - if (isset(self::$translations[$key])) { - return self::$translations[$key]; - } - return "##${key}##"; - } - - /** - * Setter to configure a custom - * primary locale for testing. - * - * @param string $primaryLocale - */ - public static function setPrimaryLocale($primaryLocale) - { - self::$primaryLocale = $primaryLocale; - } - - /** - * Mocked method - * - * @return string - */ - public static function getPrimaryLocale() - { - return self::$primaryLocale; - } - - /** - * Setter to configure a custom - * primary locale for testing. - * - * @param array $supportedLocales - * example array( - * 'en_US' => 'English', - * 'de_DE' => 'German' - * ) - */ - public static function setSupportedLocales($supportedLocales) - { - self::$supportedLocales = $supportedLocales; - } - - /** - * Mocked method - * - * @return array - */ - public static function getSupportedLocales() - { - return self::$supportedLocales; - } - - /** - * Mocked method - * - * @return array - */ - public static function getSupportedFormLocales() - { - return ['en_US']; - } - - /** - * Set translation keys to be faked. - * - * @param array $translations - */ - public static function setTranslations($translations) - { - self::$translations = $translations; - } -} - -if (!PKP_STRICT_MODE) { - class_alias('\APP\i18n\AppLocale', '\AppLocale'); -} diff --git a/tests/mock/env1/MockLocale.inc.php b/tests/mock/env1/MockLocale.inc.php new file mode 100755 index 00000000000..0af517adb42 --- /dev/null +++ b/tests/mock/env1/MockLocale.inc.php @@ -0,0 +1,122 @@ + 'English/America']; + protected array $translations = []; + + /** + * Mocked method + * + * @param $key string + * @param $params array named substitution parameters + * @param $locale string the locale to use + * @return string + */ + public function get($key, array $params = [], $locale = null): string + { + if (isset($this->translations[$key])) { + return $this->translations[$key]; + } + return "##${key}##"; + } + + /** + * Set translation keys to be faked. + * + * @param $translations array + */ + public function setTranslations(array $translations): void + { + $this->translations = $translations; + } + + /** + * Method required during setup of the PKP application framework + */ + public function getLocale(): string + { + return 'en_US'; + } + + /** + * Mocked method + */ + public function getLocales(): array + { + return [ + 'en_US' => MockLocaleMetadata::create('en_US', true), + 'pt_BR' => MockLocaleMetadata::create('pt_BR'), + 'pt_PT' => MockLocaleMetadata::create('pt_PT'), + 'de_DE' => MockLocaleMetadata::create('de_DE') + ]; + } + + /** + * Setter to configure a custom primary locale for testing. + */ + public function setPrimaryLocale(string $primaryLocale): void + { + $this->primaryLocale = $primaryLocale; + } + + /** + * Mocked method + */ + public function getPrimaryLocale(): string + { + return $this->primaryLocale; + } + + /** + * Setter to configure a custom primary locale for testing. + * + * @param array $supportedLocales + * example [ + * 'en_US' => 'English', + * 'de_DE' => 'German' + * ] + */ + public function setSupportedLocales(array $supportedLocales) + { + $this->supportedLocales = $supportedLocales; + } + + /** + * Mocked method + */ + public function getSupportedLocales(): array + { + return $this->supportedLocales; + } + + /** + * Mocked method + */ + public function getSupportedFormLocales(): array + { + return ['en_US']; + } +} + +// Replace facade +LocaleFacade::swap(new MockLocale()); diff --git a/tests/mock/env1/MockLocaleMetadata.inc.php b/tests/mock/env1/MockLocaleMetadata.inc.php new file mode 100644 index 00000000000..0dcd754c35b --- /dev/null +++ b/tests/mock/env1/MockLocaleMetadata.inc.php @@ -0,0 +1,33 @@ +isComplete; + } + + public static function create(string $locale, bool $isComplete = false): self + { + $instance = parent::create($locale); + $instance->isComplete = $isComplete; + return $instance; + } +} diff --git a/tests/mock/env2/MockAppLocale.inc.php b/tests/mock/env2/MockLocale.inc.php similarity index 69% rename from tests/mock/env2/MockAppLocale.inc.php rename to tests/mock/env2/MockLocale.inc.php index 63898ab50dd..ef560e3606d 100644 --- a/tests/mock/env2/MockAppLocale.inc.php +++ b/tests/mock/env2/MockLocale.inc.php @@ -1,7 +1,7 @@ Config::getVar('i18n', 'client_charset'), 'connectionCharset' => Config::getVar('i18n', 'connection_charset'), 'databaseDriver' => Config::getVar('database', 'driver'), 'databaseHost' => Config::getVar('database', 'host'), diff --git a/tests/prependCoverageReport.php b/tests/prependCoverageReport.php index db5b14b1d17..51a6315aaaa 100644 --- a/tests/prependCoverageReport.php +++ b/tests/prependCoverageReport.php @@ -14,9 +14,9 @@ * * @see tools/runAllTests.sh */ -$GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = get_cfg_var('phpunit_coverage_data_directory'); -include get_cfg_var('selenium_coverage_prepend_file'); +$GLOBALS['PHPUNIT_COVERAGE_DATA_DIRECTORY'] = ini_get('phpunit_coverage_data_directory'); +include ini_get('selenium_coverage_prepend_file'); if (basename($_SERVER['SCRIPT_NAME']) == 'phpunit_coverage.php') { - chdir(get_cfg_var('phpunit_coverage_data_directory')); + chdir(ini_get('phpunit_coverage_data_directory')); } diff --git a/tests/registry/locales.xml b/tests/registry/locales.xml deleted file mode 100644 index a4975a9ceb0..00000000000 --- a/tests/registry/locales.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - diff --git a/tools/copyAccessLogFileTool.php b/tools/copyAccessLogFileTool.php index eb00827e231..43fa2fb6a76 100644 --- a/tools/copyAccessLogFileTool.php +++ b/tools/copyAccessLogFileTool.php @@ -38,9 +38,6 @@ class CopyAccessLogFileTool extends \PKP\cliTool\CommandLineTool public function __construct($argv = []) { parent::__construct($argv); - - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_ADMIN); - if (count($this->argv) < 1 || count($this->argv) > 2) { $this->usage(); exit(1); diff --git a/tools/installEmailTemplate.php b/tools/installEmailTemplate.php index 45a14cd2524..bece985a707 100644 --- a/tools/installEmailTemplate.php +++ b/tools/installEmailTemplate.php @@ -63,9 +63,6 @@ public function execute() { // Load the necessary locale data $locales = explode(',', $this->_locales); - foreach ($locales as $locale) { - AppLocale::requireComponents(LOCALE_COMPONENT_APP_EMAIL, $locale); - } // Install to the database Repo::emailTemplate()->dao->installEmailTemplates(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), $locales, $this->_emailKey); diff --git a/tools/installPluginVersion.php b/tools/installPluginVersion.php index 08e5c130619..b830cea9fa5 100644 --- a/tools/installPluginVersion.php +++ b/tools/installPluginVersion.php @@ -13,16 +13,14 @@ * @brief CLI tool for installing a plugin version descriptor. */ -define('RUNNING_UPGRADE', 1); - require(dirname(__FILE__, 4) . '/tools/bootstrap.inc.php'); use APP\core\Application; use APP\install\Upgrade; -use PKP\site\Version; - use PKP\site\VersionCheck; +Application::upgrade(); + class InstallPluginVersionTool extends \PKP\cliTool\CommandLineTool { /** @var string Path to descriptor file to install */ diff --git a/tools/jobs.php b/tools/jobs.php index 089ee1482af..5b0105d952d 100644 --- a/tools/jobs.php +++ b/tools/jobs.php @@ -18,7 +18,6 @@ namespace PKP\tools; use APP\facades\Repo; -use APP\i18n\AppLocale; use Illuminate\Console\Concerns\InteractsWithIO; use Illuminate\Console\OutputStyle; use PKP\cliTool\CommandLineTool; @@ -100,8 +99,6 @@ public function __construct($argv = []) { parent::__construct($argv); - AppLocale::requireComponents(LOCALE_COMPONENT_PKP_ADMIN); - array_shift($argv); $this->setParameterList($argv);