diff --git a/CHANGES.md b/CHANGES.md
index f81e1dbfb..17bd6a1a3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,10 @@
# OPUS 4 Change Log
+## Release 4.10 - 2026-05-12
+
+OPUS 4.10 Project on GitHub
+https://github.com/orgs/OPUS4/projects/74
+
## Release 4.9 - 2026-04-14
OPUS 4.9 Project on GitHub
diff --git a/README.md b/README.md
index 91c6eb755..2c5dd05a5 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ mostly.
## OPUS 4
-The current version of OPUS 4 is __4.9__. It is available on the [master][MASTER] branch and compatible with
+The current version of OPUS 4 is __4.10__. It is available on the [master][MASTER] branch and compatible with
PHP 8.1 to 8.2. PHP 8.3 and beyond are not supported yet.
[Documentation][DOC]
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 6f66e43a7..ce1ef9993 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,69 @@
# OPUS 4 Release Notes
+## Release 4.10 - 2026-05-12
+
+Der neue Release bringt insbesondere Veränderungen bei der Sprachverwaltung und
+den Optionen, die in der Administration editierbar sind. Weiterhin gab es eine
+Reihe kleinerer Fehlerbehebungen.
+
+### Update auf OPUS 4.10
+
+Das Updateskript, `bin/update.sh`, muss ausgeführt werden, da es Änderungen an
+der Datenbank und weitere Updateschritte gibt.
+
+- Tabelle `configuration` wird angelegt
+- Tabelle `languages` wird entfernt
+- Konfiguration in `config.xml` with in die Datenbank übertragen und die
+ Datei optional gelöscht
+- Aktive Sprachen werden in neue Konfigurationsoptionen übernommen
+
+### Sprachverwaltung
+
+Die Sprachverwaltung wurde aus der Administration entfernt. Dafür werden jetzt
+485 ISO 639 Sprachen im Standard unterstützt. Welche Sprachen in den Formularen
+zur Auswahl stehen sollen, lässt sich über Optionen einstellen.
+
+Mehr dazu findet sich im OPUS 4 Handbuch unter
+https://www.opus-repository.org/userdoc/admin/languages.html
+
+Beim Update werden die aktiven Sprachen automatisch in die neue Konfiguration
+übernommen.
+
+### Konfiguration
+
+Bislang sind nur wenige Optionen in der Administration editierbar. Diese wurde
+bisher in `application/configs/config.xml` gespeichert. Dafür gibt es jetzt eine
+Tabelle in der Datenbank. Beim Update wird der Inhalt von `config.xml` in die
+Datenbank übertragen und die Datei dann (optional) gelöscht.
+
+Die editierbaren Optionen werden nun in `application/configs/options.yml`
+definiert. Generell kann die Liste lokal erweitert werden. Im Standard werden
+im Laufe der Zeit mehr Optionen in der Weboberfläche verfügbar gemacht werden.
+
+### RSS-Links
+
+RSS-Links können nun ausgeblendet werden. Sie werden automatisch ausgeblendet,
+wenn ein User keinen Zugriff auf das RSS-Modul hat.
+
+ rss.showLinks = 0
+
+### DeepGreen Client
+
+Es gibt zwei neue Optionen, die steuern wie beim Import von Dokumenten mit
+nicht erlaubten Dateitypen umgegangen werden soll.
+
+ deepgreen.import.importAllFiles = 0
+ deepgreen.import.importSupportedFiles = 1
+
+Wenn **importAllFiles** aktiviert ist, werden alle Dateien importiert. Sollen
+nur die in der OPUS 4 Konfiguration erlaubt Dateitypen importiert werden, kann
+**importSupportedFiles** aktiviert werden. Sind beide Optionen deaktiviert,
+werden Dokumente mit nicht erlaubten Dateitypen nicht importiert. Es geplant
+in diesen Fällen in Zukunft Benachrichtigungen in der Administration anzuzeigen
+und die Konfigurationsmöglichkeiten weiter auszubauen.
+
+--
+
## Release 4.9 - 2026-04-14
### Unterstützte PHP-Versionen
diff --git a/application/configs/application.ini b/application/configs/application.ini
index 9e941e643..093d6ffe7 100644
--- a/application/configs/application.ini
+++ b/application/configs/application.ini
@@ -61,7 +61,11 @@ db.debug = 0
; LOCALE SETTINGS
resources.locale.default = 'de'
-; SUPPORTED LANGUAGES
+; Languages selectable for documents
+i18n.languages.active = deu, eng, fra, rus, spa, mul
+i18n.languages.sortByName = 0
+
+; SUPPORTED USER INTERFACE LANGUAGES
supportedLanguages = de,en
;GENERAL SETTINGS
@@ -70,7 +74,7 @@ name = 'OPUS 4'
logoLink = home
security = 1
workspacePath = APPLICATION_PATH "/workspace"
-version = 4.9
+version = 4.10
update.latestVersionCheckUrl = "https://api.github.com/repos/opus4/application/releases/latest"
snippets.basePath = APPLICATION_PATH "/scripts/snippets"
@@ -250,6 +254,7 @@ search.index.enrichment.blacklist = 'opus_doi_json'
; RSS Feed
rss.maxDocs = 100
+rss.showLinks = 1
;DOCTYPE VALIDATION SCHEMA FILE
; TODO determine path dynamically (does this belong into the framework)
@@ -761,6 +766,9 @@ console.commandProvider[] = "Opus\Import\Console\ImportCommandProvider"
; DeepGreen configuration
deepgreen.configFile = APPLICATION_PATH'/application/configs/deepgreen.ini'
+deepgreen.import.importAllFiles = 0
+deepgreen.import.importSupportedFiles = 1
+
; Staging, Testing and Development configurations =====================================================================
[staging : production]
diff --git a/application/configs/navigationModules.xml b/application/configs/navigationModules.xml
index 9f00eac02..ce1a1cf7c 100644
--- a/application/configs/navigationModules.xml
+++ b/application/configs/navigationModules.xml
@@ -364,50 +364,6 @@
-
- mvc
-
- admin_title_languages_description
- group-cafelatte
- admin
- language
- index
- languages
-
-
- mvc
-
- admin
- language
- show
-
-
-
- mvc
-
- admin
- language
- new
-
-
-
- mvc
-
- admin
- language
- edit
-
-
-
- mvc
-
- admin
- language
- delete
-
-
-
-
mvc
diff --git a/application/configs/options.yml b/application/configs/options.yml
new file mode 100644
index 000000000..b4c1e7851
--- /dev/null
+++ b/application/configs/options.yml
@@ -0,0 +1,44 @@
+#
+# Defines options available in administration user interface (Settings->Options).
+#
+# The 'type' defines how the option appears in the form. Supported types are
+# - string (default)
+# - int
+# - bool
+#
+# The 'section' allows grouping options. The names are arbitrary and are used as section
+# labels if no translations for the section exist.
+#
+# 'options' can be used to pass additional options to the form element, for
+# instance to increase the size.
+#
+# The translation keys for options use the following patterns:
+# - admin_config_section_SECTIONNAME
+# - admin_config_OPTIONKEY
+# - admin_config_OPTIONKEY_description
+#
+# For instance:
+# - admin_config_section_languages
+# - admin_config_i18n.languages.active
+# - admin_config_i18n.languages.active_description
+#
+
+searchengine.solr.parameterDefaults.rows:
+ type: int
+ section: searching
+ options:
+ min: 10
+
+browsing.series.sortByTitle:
+ type: bool
+ section: browsing
+
+i18n.languages.active:
+ type: string
+ section: languages
+ options:
+ size: 60
+
+i18n.languages.sortByName:
+ type: bool
+ section: languages
diff --git a/bin/install.sh b/bin/install.sh
index 9faf92435..86ed33af6 100755
--- a/bin/install.sh
+++ b/bin/install.sh
@@ -174,7 +174,7 @@ do
fi
done
-php "$BASEDIR/scripts/change-password.php" admin "$ADMIN_PWD"
+php "$BASEDIR/bin/opus4" account:setpwd admin -p "$ADMIN_PWD"
#
# Configure Solr connection
diff --git a/build.xml b/build.xml
index 02445f212..43962e401 100644
--- a/build.xml
+++ b/build.xml
@@ -316,9 +316,11 @@
-
-
+
+
+
+
diff --git a/composer.json b/composer.json
index 787b8eaa2..f80e6a7a9 100644
--- a/composer.json
+++ b/composer.json
@@ -23,17 +23,18 @@
"ext-yaml": "*",
"opus4/zf1-future": "1.25.*",
"jpgraph/jpgraph": "dev-master",
- "opus4-repo/opus4-common": "^4.9",
- "opus4-repo/framework": "^4.9",
+ "opus4-repo/opus4-common": "^4.10",
+ "opus4-repo/framework": "^4.10",
"opus4-repo/search": "^4.9",
- "opus4-repo/opus4-bibtex": "^4.9",
- "opus4-repo/opus4-import": "^4.9",
+ "opus4-repo/opus4-bibtex": "^4.10",
+ "opus4-repo/opus4-import": "^4.10",
"opus4-repo/opus4-pdf": "^4.9",
"opus4-repo/opus4-job": "^4.9",
"opus4-repo/opus4-security": "^4.9",
"opus4-repo/opus4-sword": "^4.9",
- "opus4-repo/opus4-app-common": "^4.9",
- "opus4-repo/opus4-deepgreen": "^4.9",
+ "opus4-repo/opus4-app-common": "^4.10",
+ "opus4-repo/opus4-deepgreen": "^4.10",
+ "opus4-repo/opus4-i18n": "^4.10",
"components/jquery": "3.4.*",
"components/jqueryui": "1.12.*",
"oomphinc/composer-installers-extender": "^2.0",
diff --git a/db/masterdata/002_create_languages.sql b/db/masterdata/002_create_languages.sql
deleted file mode 100644
index 6f44a3bd6..000000000
--- a/db/masterdata/002_create_languages.sql
+++ /dev/null
@@ -1,44 +0,0 @@
--- MySQL dump 10.11
---
--- Server version 5.0.67-0ubuntu6
-
-/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
-/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
-/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
-/*!40101 SET NAMES utf8 */;
-/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
-/*!40103 SET TIME_ZONE='+00:00' */;
-/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
-/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
-/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
-/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-
---
--- Dumping data for table `languages`
--- Based on http://sil.org/iso639-3/iso-639-3_20090210.tab
---
-
-LOCK TABLES `languages` WRITE;
-/*!40000 ALTER TABLE `languages` DISABLE KEYS */;
-INSERT INTO `languages` (`id`, `part2_b`, `part2_t`, `part1`, `scope`, `type`, `ref_name`, `comment`, `active`) VALUES
-('1','ger','deu','de','I','L','German','',1),
-('2','eng','eng','en','I','L','English','',1),
-('3','ita','ita','it','I','L','Italian','',0),
-('4','fre','fra','fr','I','L','French','',1),
-('5','por','por','pt','I','L','Portuguese','',0),
-('6','rus','rus','ru','I','L','Russian','',1),
-('7','spa','spa','es','I','L','Spanish','',1),
-('8','mul','mul','','I','L','Multiple languages','',1);
-/*!40000 ALTER TABLE `languages` ENABLE KEYS */;
-UNLOCK TABLES;
-/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
-
-/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
-/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
-/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
-/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
-/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
-/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
-/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-
--- Dump completed on 2009-04-09 8:56:35
diff --git a/db/masterdata/022-set-opus-version.sql b/db/masterdata/022-set-opus-version.sql
index 9af826166..d2316260f 100644
--- a/db/masterdata/022-set-opus-version.sql
+++ b/db/masterdata/022-set-opus-version.sql
@@ -11,7 +11,7 @@ START TRANSACTION;
-- Set internal OPUS version (for controlling updates)
TRUNCATE TABLE `opus_version`;
-INSERT INTO `opus_version` (`version`) VALUES (20);
+INSERT INTO `opus_version` (`version`) VALUES (22);
COMMIT;
diff --git a/library/Application/Bootstrap.php b/library/Application/Bootstrap.php
index 4ed1957ab..d3b1d2783 100644
--- a/library/Application/Bootstrap.php
+++ b/library/Application/Bootstrap.php
@@ -30,9 +30,11 @@
*/
use Opus\App\Common\Configuration;
+use Opus\Common\Config;
use Opus\Common\Log\LogService;
use Opus\Common\Repository;
use Opus\Db\DatabaseBootstrap;
+use Opus\Db2\Configuration as ConfigurationDatabase;
use Opus\Search\Plugin\Index;
/**
@@ -258,7 +260,7 @@ protected function _setupPageCache()
*/
protected function _initTranslation()
{
- $this->bootstrap(['Configuration', 'Session', 'Logging', 'ZendCache']);
+ $this->bootstrap(['Configuration', 'OnlineConfiguration', 'Session', 'Logging', 'ZendCache']);
$logService = LogService::getInstance();
$logger = $logService->getLog('translation');
@@ -411,4 +413,15 @@ protected function _initIndexPlugin()
// TODO this is a dependency on a specific implementation (refactor to remove)
$cache::setIndexPluginClass(Index::class);
}
+
+ protected function _initOnlineConfiguration()
+ {
+ $this->bootstrap('Database');
+
+ $configuration = new ConfigurationDatabase();
+ $onlineConfig = $configuration->getConfig();
+
+ $config = Config::get();
+ $config->merge($onlineConfig);
+ }
}
diff --git a/library/Application/Console/Admin/ChangePasswordCommand.php b/library/Application/Console/Admin/ChangePasswordCommand.php
new file mode 100644
index 000000000..8b1423ac5
--- /dev/null
+++ b/library/Application/Console/Admin/ChangePasswordCommand.php
@@ -0,0 +1,128 @@
+--password option can be used to provide a new password without interaction, however this should be used carefully, since the password won't be hidden.
+EOT;
+
+ $this->setName('account:setpwd')
+ ->setDescription('Set user password')
+ ->setHelp($help)
+ ->addArgument(
+ self::ARGUMENT_USER,
+ InputArgument::REQUIRED,
+ 'User login'
+ )
+ ->addOption(
+ self::OPTION_PASSWORD,
+ '-p',
+ InputOption::VALUE_REQUIRED,
+ 'New password'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $formatter = new FormatterHelper();
+
+ $login = $input->getArgument(self::ARGUMENT_USER);
+
+ try {
+ $account = Account::fetchAccountByLogin($login);
+ } catch (SecurityException $e) {
+ $formatted = $formatter->formatBlock('User not found', 'error');
+ $output->writeln($formatted);
+ return self::FAILURE;
+ }
+
+ $password = $input->getOption(self::OPTION_PASSWORD);
+
+ if ($password === null) {
+ $helper = new QuestionHelper();
+
+ $question = new Question('New password: ');
+ $question->setHidden(true);
+ $question->setHiddenFallback(false);
+ $password = $helper->ask($input, $output, $question);
+
+ $question = new Question('Confirm password: ');
+ $question->setHidden(true);
+ $question->setHiddenFallback(false);
+ $confirm = $helper->ask($input, $output, $question);
+
+ if ($password !== $confirm) {
+ $formatted = $formatter->formatBlock('Passwords do not match.', 'error');
+ $output->writeln($formatted);
+ return self::FAILURE;
+ }
+ }
+
+ try {
+ $account->setPassword($password)->store();
+ } catch (SecurityException $e) {
+ $formatted = $formatter->formatBlock('Setting password failed', 'error');
+ $output->writeln($formatted);
+ return self::FAILURE;
+ }
+
+ $output->writeln('Password successfully changed.');
+
+ return self::SUCCESS;
+ }
+}
diff --git a/library/Application/Console/App.php b/library/Application/Console/App.php
index 901f6b926..4dfd0eef0 100644
--- a/library/Application/Console/App.php
+++ b/library/Application/Console/App.php
@@ -74,6 +74,8 @@ public function __construct()
$this->add(new Application_Console_Collection_MoveCommand());
$this->add(new Application_Console_Collection_RemoveCommand());
+ $this->add(new Application_Console_Admin_ChangePasswordCommand());
+
if (class_exists(TaskManager::class)) {
/*
Tasks commands do not work without the TaskManager. If the current PHP version is less than 7.4
diff --git a/library/Application/Controller/Action/Helper/Translation.php b/library/Application/Controller/Action/Helper/Translation.php
index 17574d8c8..b92a9940c 100644
--- a/library/Application/Controller/Action/Helper/Translation.php
+++ b/library/Application/Controller/Action/Helper/Translation.php
@@ -31,7 +31,6 @@
use Opus\Common\Enrichment;
use Opus\Document;
-use Opus\Language;
/**
* Helper for handling translations.
@@ -103,12 +102,7 @@ public function getKeyForField($modelName, $fieldName)
$translationKey = $this->normalizeModelName($modelName) . '_' . $fieldName;
return preg_replace('/Opus_Common_/', 'Opus_', $translationKey); // TODO LAMINAS fix keys
} else {
- switch ($modelName) {
- case Language::class:
- return $this->normalizeModelName($modelName) . '_' . $fieldName;
- default:
- return $fieldName;
- }
+ return $fieldName;
}
}
diff --git a/library/Application/Form/Element/Language.php b/library/Application/Form/Element/Language.php
index acfe7e09f..7089bc0d7 100644
--- a/library/Application/Form/Element/Language.php
+++ b/library/Application/Form/Element/Language.php
@@ -29,7 +29,10 @@
* @license http://www.gnu.org/licenses/gpl.html General Public License
*/
-use Opus\Common\Language;
+use Opus\App\Common\Configuration;
+use Opus\Common\Log;
+use Opus\I18n\I18nException;
+use Opus\I18n\Languages;
/**
* TODO override setLabel for more robust translation
@@ -52,6 +55,31 @@ public function init()
}
}
+ /**
+ * @param string|null $value
+ * @return void
+ */
+ public function setValue($value)
+ {
+ if ($value !== null) {
+ $language = Languages::getLanguage($value);
+
+ if (null !== $language) {
+ $value = $language->getId();
+ }
+ }
+
+ if (! in_array($value, array_keys(self::getLanguageList()))) {
+ $label = Locale::getDisplayName($value);
+ if ($label !== $value) {
+ $label .= " ({$value})";
+ }
+ $this->addMultiOption($value, $label);
+ }
+
+ parent::setValue($value);
+ }
+
/**
* @return array
*/
@@ -65,15 +93,68 @@ public static function getLanguageList()
/**
* Setup language list.
+ *
+ * TODO reduce responsibilities of this function
*/
public static function initLanguageList()
{
+ $config = Configuration::getInstance()->getConfig();
+
+ if (! isset($config->i18n->languages->active)) {
+ throw new Exception('no active languages configured');
+ }
+
+ $optionValue = $config->i18n->languages->active;
+
+ if (strlen(trim($optionValue)) > 0) {
+ // Use configured languages
+ $activeLanguages = explode(',', $optionValue);
+ } else {
+ // Use all languages
+ $helper = new Languages();
+ $activeLanguages = array_keys($helper->getAllAsArray());
+ }
+
+ $activeLanguages = array_filter($activeLanguages, function ($lang) {
+ return ! empty($lang);
+ });
+
+ if (isset($config->i18n->languages->local)) {
+ $localLanguages = $config->i18n->languages->local->toArray();
+ $languages = new Languages();
+ try {
+ $languages->addLanguages($localLanguages);
+ } catch (I18nException $ex) {
+ Log::get()->err('Error loading local languages: ' . $ex->getMessage());
+ }
+ }
+
$translate = Application_Translate::getInstance();
+ $locale = $translate->getLocale();
+
$languages = [];
- foreach (Language::getAllActiveTable() as $languageRow) {
- $langId = $languageRow['part2_t'];
- $languages[$langId] = $translate->translateLanguage($langId);
+
+ foreach ($activeLanguages as $lang) {
+ $part2b = trim($lang);
+ $language = Languages::getLanguage($part2b);
+
+ if ($language === null) {
+ Log::get()->err("Language '{$part2b}' not found");
+ continue;
+ }
+
+ $langId = $language->getPart2t();
+ $translation = $language->getDisplayName($locale);
+
+ // TODO support local translations
+
+ $languages[$langId] = $translation;
}
+
+ if (isset($config->i18n->languages->sortByName) && filter_var($config->i18n->languages->sortByName, FILTER_VALIDATE_BOOLEAN)) {
+ asort($languages);
+ }
+
self::$languageList = $languages;
}
}
diff --git a/library/Application/Form/Element/SupportedLanguages.php b/library/Application/Form/Element/SupportedLanguages.php
index 00392334e..72cfbecc7 100644
--- a/library/Application/Form/Element/SupportedLanguages.php
+++ b/library/Application/Form/Element/SupportedLanguages.php
@@ -116,7 +116,7 @@ public function getLanguageOptions()
*/
public function setValue($value)
{
- if (! is_array($value)) {
+ if (! is_array($value) && $value !== null) {
$values = array_map('trim', explode(',', $value));
} else {
$values = $value;
diff --git a/library/Application/Security/AclProvider.php b/library/Application/Security/AclProvider.php
index 678ba3519..3d7256192 100644
--- a/library/Application/Security/AclProvider.php
+++ b/library/Application/Security/AclProvider.php
@@ -65,7 +65,6 @@ class Application_Security_AclProvider
'licences',
'collections',
'series',
- 'languages',
'statistics',
'institutions',
'enrichments',
diff --git a/library/Application/View/Helper/DocumentAbstract.php b/library/Application/View/Helper/DocumentAbstract.php
index 75b534ea7..5a0ba612b 100644
--- a/library/Application/View/Helper/DocumentAbstract.php
+++ b/library/Application/View/Helper/DocumentAbstract.php
@@ -30,7 +30,7 @@
*/
use Opus\Common\DocumentInterface;
-use Opus\Common\Language;
+use Opus\I18n\Languages;
/**
* Helper for printing the abstract of a OPUS document.
@@ -48,7 +48,7 @@ class Application_View_Helper_DocumentAbstract extends Application_View_Helper_D
public function documentAbstract($document = null)
{
if ($this->isPreferUserInterfaceLanguage()) {
- $language = Language::getPart2tForPart1(Application_Translate::getInstance()->getLocale());
+ $language = Languages::getPart2t(Application_Translate::getInstance()->getLocale());
$abstract = $document->getMainAbstract($language);
} else {
diff --git a/library/Application/View/Helper/DocumentTitle.php b/library/Application/View/Helper/DocumentTitle.php
index e62177eed..9672553d8 100644
--- a/library/Application/View/Helper/DocumentTitle.php
+++ b/library/Application/View/Helper/DocumentTitle.php
@@ -30,7 +30,7 @@
*/
use Opus\Common\DocumentInterface;
-use Opus\Common\Language;
+use Opus\I18n\Languages;
/**
* Helper for printing the title of a OPUS document.
@@ -55,7 +55,7 @@ class Application_View_Helper_DocumentTitle extends Application_View_Helper_Docu
public function documentTitle($document = null)
{
if ($this->isPreferUserInterfaceLanguage()) {
- $language = Language::getPart2tForPart1(Application_Translate::getInstance()->getLocale());
+ $language = Languages::getPart2t(Application_Translate::getInstance()->getLocale());
$title = $document->getMainTitle($language);
} else {
diff --git a/library/Application/View/Helper/LanguageWebForm.php b/library/Application/View/Helper/LanguageWebForm.php
index 2bad888c3..a102ee810 100644
--- a/library/Application/View/Helper/LanguageWebForm.php
+++ b/library/Application/View/Helper/LanguageWebForm.php
@@ -29,7 +29,7 @@
* @license http://www.gnu.org/licenses/gpl.html General Public License
*/
-use Opus\Common\Language;
+use Opus\I18n\Languages;
/**
* View helper for tranform long language form in short language form (Part2 in Part1).
@@ -55,8 +55,7 @@ class Application_View_Helper_LanguageWebForm extends Zend_View_Helper_Abstract
public function languageWebForm($value)
{
if (! array_key_exists($value, $this->langCache)) {
- $lang = Language::getPropertiesByPart2T($value);
- $this->langCache[$value] = $lang['part1'];
+ $this->langCache[$value] = Languages::getPart1($value);
}
return $this->langCache[$value];
}
diff --git a/library/Application/View/Helper/RssLink.php b/library/Application/View/Helper/RssLink.php
new file mode 100644
index 000000000..9b1e3d0c8
--- /dev/null
+++ b/library/Application/View/Helper/RssLink.php
@@ -0,0 +1,85 @@
+isShowRssLinks() || ! $this->isRssAllowed()) {
+ return '';
+ }
+
+ $view = $this->view;
+
+ if (is_string($options)) {
+ $rssUrl = $options;
+ } else {
+ $basicOptions = [
+ 'module' => 'rss',
+ 'controller' => 'index',
+ 'action' => 'index',
+ ];
+
+ if (is_array($options)) {
+ $rssUrl = $view->url(array_merge($basicOptions, $options), null, true);
+ } else {
+ $rssUrl = $view->url($basicOptions, null, true);
+ }
+ }
+
+ $imagePath = $view->layoutPath() . '/img/feed_small.png';
+ $alt = $view->translate('rss_icon');
+ $title = $view->translate('rss_title');
+
+ $output = "";
+
+ return $output;
+ }
+
+ protected function isShowRssLinks(): bool
+ {
+ $config = $this->getConfig();
+ return isset($config->rss->showLinks) && filter_var($config->rss->showLinks, FILTER_VALIDATE_BOOLEAN);
+ }
+
+ protected function isRssAllowed(): bool
+ {
+ $realm = Realm::getInstance();
+ return $realm->checkModule('rss');
+ }
+}
diff --git a/library/Application/Form/Element/LanguageType.php b/library/Application/View/Helper/RssMetaLink.php
similarity index 70%
rename from library/Application/Form/Element/LanguageType.php
rename to library/Application/View/Helper/RssMetaLink.php
index b00fafcea..9b9ad5710 100644
--- a/library/Application/Form/Element/LanguageType.php
+++ b/library/Application/View/Helper/RssMetaLink.php
@@ -25,20 +25,34 @@
* along with OPUS; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
- * @copyright Copyright (c) 2008, OPUS 4 development team
+ * @copyright Copyright (c) 2026, OPUS 4 development team
* @license http://www.gnu.org/licenses/gpl.html General Public License
*/
-class Application_Form_Element_LanguageType extends Application_Form_Element_SelectWithNull
+/**
+ * TODO check permissions and options
+ * TODO unit tests
+ */
+class Application_View_Helper_RssMetaLink extends Application_View_Helper_RssLink
{
- public function init()
+ public function rssMetaLink(?string $rssUrl = null): string
{
- parent::init();
+ if (! $this->isShowRssLinks() || ! $this->isRssAllowed()) {
+ return '';
+ }
- $values = ['Null', 'A', 'C', 'E', 'H', 'L', 'S'];
+ $view = $this->view;
- foreach ($values as $value) {
- $this->addMultiOption($value, 'Opus_Language_Type_Value_' . $value);
+ if ($rssUrl === null) {
+ $rssUrl = $view->baseUrl() . '/rss/index/index';
}
+
+ $view->headLink([
+ 'rel' => 'alternate',
+ 'type' => 'application/rss+xml',
+ 'href' => $view->serverUrl() . $rssUrl,
+ ]);
+
+ return '';
}
}
diff --git a/modules/admin/controllers/ConfigController.php b/modules/admin/controllers/ConfigController.php
index 167eace80..032f5bf15 100644
--- a/modules/admin/controllers/ConfigController.php
+++ b/modules/admin/controllers/ConfigController.php
@@ -29,8 +29,6 @@
* @license http://www.gnu.org/licenses/gpl.html General Public License
*/
-use Opus\App\Common\Configuration;
-
class Admin_ConfigController extends Application_Controller_Action
{
public function indexAction()
@@ -48,8 +46,7 @@ public function indexAction()
case Admin_Form_Configuration::RESULT_SAVE:
if ($form->isValid($data)) {
$config = new Zend_Config([], true);
- $form->updateModel($config);
- Configuration::save($config);
+ $form->updateModel($config); // TODO $config object is not needed
} else {
break;
}
diff --git a/modules/admin/controllers/LanguageController.php b/modules/admin/controllers/LanguageController.php
deleted file mode 100644
index 52e7238ea..000000000
--- a/modules/admin/controllers/LanguageController.php
+++ /dev/null
@@ -1,57 +0,0 @@
-setFormClass(Admin_Form_Language::class);
- parent::init();
- }
-
- /**
- * @param LanguageInterface $model
- * @return bool
- */
- public function isDeletable($model)
- {
- return ! $model->isUsed();
- }
-}
diff --git a/modules/admin/forms/Configuration.php b/modules/admin/forms/Configuration.php
index b65dcbd60..3fb9d6375 100644
--- a/modules/admin/forms/Configuration.php
+++ b/modules/admin/forms/Configuration.php
@@ -33,25 +33,20 @@
/**
* Form for editing selected OPUS 4 configuration options.
- *
- * TODO Application_Form_Abstract should be enough (not ID element needed)
*/
class Admin_Form_Configuration extends Application_Form_Model_Abstract
{
/**
* Prefix for translation keys of configuration options.
*
- * TODO wird auf von Admin_Model_Option verwendet
+ * TODO wird auch von Admin_Model_Option verwendet
*/
public const LABEL_TRANSLATION_PREFIX = 'admin_config_';
/** @var array Configured options for form. */
private $options;
- /**
- * @param null|Zend_Config $config
- */
- public function __construct($config = null)
+ public function __construct(?array $config = null)
{
if ($config !== null) {
$options = new Admin_Model_Options($config);
@@ -73,19 +68,27 @@ public function init()
$this->options = $options->getOptions();
}
- foreach ($this->options as $name => $option) {
+ foreach ($this->options as $option) {
$section = $option->getSection();
+ $elementOptions = $option->getOptions();
+
+ $translator = $this->getTranslator();
+
+ if ($translator->isTranslated($option->getDescription())) {
+ $elementOptions['description'] = $option->getDescription();
+ }
+
+ if ($translator->isTranslated($option->getLabel())) {
+ $elementOptions['label'] = $option->getLabel();
+ } else {
+ $elementOptions['label'] = $option->getKey();
+ }
+
$element = $this->createElement(
$option->getElementType(),
- $name,
- array_merge(
- [
- 'label' => $option->getLabel(),
- 'description' => $option->getDescription(),
- ],
- $option->getOptions()
- )
+ $option->getElementId(),
+ $elementOptions
);
$this->addElement($element);
@@ -95,6 +98,8 @@ public function init()
$this->removeElement(self::ELEMENT_MODEL_ID);
$this->setAttrib('class', 'admin_config');
+
+ $this->sortSections();
}
/**
@@ -104,9 +109,9 @@ public function init()
*/
public function populateFromModel($config)
{
- foreach ($this->options as $name => $option) {
+ foreach ($this->options as $option) {
$value = Configuration::getValueFromConfig($config, $option->getKey());
- $this->getElement($name)->setValue($value);
+ $this->getElement($option->getElementId())->setValue($value);
}
}
@@ -117,15 +122,18 @@ public function populateFromModel($config)
*/
public function updateModel($config)
{
- foreach ($this->options as $name => $option) {
- $value = $this->getElement($name)->getValue();
+ foreach ($this->options as $option) {
+ $value = $this->getElement($option->getElementId())->getValue();
// TODO move into Admin_Model_Option?
if (is_array($value)) {
$value = implode(',', $value);
}
+ if ($value !== null && strlen(trim($value)) === 0) {
+ $value = null;
+ }
- Configuration::setValueInConfig($config, $option->getKey(), $value);
+ $option->setValue($value);
}
}
@@ -134,20 +142,25 @@ public function updateModel($config)
*
* If necessary a new display group is created.
*
- * @param Zend_Form_Element $element Form element
- * @param string $section Name of section
* @throws Zend_Form_Exception
*/
- public function addElementToSection($element, $section)
+ public function addElementToSection(Zend_Form_Element $element, string $section): void
{
$group = $this->getDisplayGroup($section);
+ $translator = $this->getTranslator();
+ $sectionKey = self::LABEL_TRANSLATION_PREFIX . 'section_' . $section;
+
+ if (! $translator->isTranslated($sectionKey)) {
+ $sectionKey = ucfirst($section);
+ }
+
if ($group === null) {
$this->addDisplayGroup(
[$element],
$section,
[
- 'legend' => self::LABEL_TRANSLATION_PREFIX . 'section_' . $section,
+ 'legend' => $sectionKey,
'decorators' => ['FormElements', 'Fieldset'],
]
);
@@ -155,4 +168,24 @@ public function addElementToSection($element, $section)
$group->addElement($element);
}
}
+
+ public function sortSections(): void
+ {
+ $groups = $this->getDisplayGroups();
+
+ $names = array_keys($groups);
+ unset($names['actions']);
+
+ sort($names);
+
+ $sorted = [];
+
+ foreach ($names as $section) {
+ $sorted[$section] = $groups[$section];
+ }
+
+ $sorted['actions'] = $groups['actions'];
+
+ $this->setDisplayGroups($sorted);
+ }
}
diff --git a/modules/admin/forms/Document/Tags.php b/modules/admin/forms/Document/Tags.php
index b7ba242b5..e6ba9093c 100644
--- a/modules/admin/forms/Document/Tags.php
+++ b/modules/admin/forms/Document/Tags.php
@@ -30,7 +30,7 @@
*/
use Opus\Common\DocumentInterface;
-use Opus\Common\Language;
+use Opus\I18m\Languages;
/**
* Unterformular fuer GND Subjects im Metadaten-Formular.
@@ -186,7 +186,7 @@ public function processPost($data, $context)
$lang = $data[self::ELEMENT_LANGUAGE];
if ($lang === null) {
$translate = Application_Translate::getInstance();
- $lang = Language::getPart2tForPart1($translate->getLocale());
+ $lang = Languages::getPart2t($translate->getLocale());
}
$this->addMultipleSubjectsFromString($data[self::ELEMENT_SUBJECTS], $lang);
return Admin_Form_Document::RESULT_SHOW;
diff --git a/modules/admin/forms/Language.php b/modules/admin/forms/Language.php
deleted file mode 100644
index b7519298a..000000000
--- a/modules/admin/forms/Language.php
+++ /dev/null
@@ -1,95 +0,0 @@
-setRemoveEmptyCheckbox(false);
- $this->setLabelPrefix('Opus_Language_');
- $this->setUseNameAsLabel(true);
- $this->setModelClass(Language::class);
-
- $this->addElement('checkbox', self::ELEMENT_ACTIVE);
- $this->addElement('text', self::ELEMENT_REFNAME, ['required' => true]);
- $this->addElement('text', self::ELEMENT_PART2T, ['required' => true]);
- $this->addElement('text', self::ELEMENT_PART2B);
- $this->addElement('text', self::ELEMENT_PART1);
- $this->addElement('LanguageScope', self::ELEMENT_SCOPE);
- $this->addElement('LanguageType', self::ELEMENT_TYPE);
- $this->addElement('text', self::ELEMENT_COMMENT);
- }
-
- /**
- * @param LanguageInterface $language
- */
- public function populateFromModel($language)
- {
- $this->getElement(self::ELEMENT_MODEL_ID)->setValue($language->getId());
- $this->getElement(self::ELEMENT_ACTIVE)->setValue($language->getActive());
- $this->getElement(self::ELEMENT_PART2B)->setValue($language->getPart2B());
- $this->getElement(self::ELEMENT_PART2T)->setValue($language->getPart2T());
- $this->getElement(self::ELEMENT_PART1)->setValue($language->getPart1());
- $this->getElement(self::ELEMENT_SCOPE)->setValue($language->getScope());
- $this->getElement(self::ELEMENT_TYPE)->setValue($language->getType());
- $this->getElement(self::ELEMENT_REFNAME)->setValue($language->getRefName());
- $this->getElement(self::ELEMENT_COMMENT)->setValue($language->getComment());
- }
-
- /**
- * @param LanguageInterface $language
- */
- public function updateModel($language)
- {
- $language->setActive($this->getElementValue(self::ELEMENT_ACTIVE));
- $language->setPart2B($this->getElementValue(self::ELEMENT_PART2B));
- $language->setPart2T($this->getElementValue(self::ELEMENT_PART2T));
- $language->setPart1($this->getElementValue(self::ELEMENT_PART1));
- $language->setScope($this->getElementValue(self::ELEMENT_SCOPE));
- $language->setType($this->getElementValue(self::ELEMENT_TYPE));
- $language->setRefName($this->getElementValue(self::ELEMENT_REFNAME));
- $language->setComment($this->getElementValue(self::ELEMENT_COMMENT));
- }
-}
diff --git a/modules/admin/language/config.tmx b/modules/admin/language/config.tmx
index 98bb735bb..0ca26d1ca 100644
--- a/modules/admin/language/config.tmx
+++ b/modules/admin/language/config.tmx
@@ -26,7 +26,7 @@
-
+
Search
@@ -49,29 +49,38 @@
Browsing
- Browsing
+ Browsen
+
+
+
+
+
+ Languages
+
+
+ Sprachen
- Supported Languages
+ User Interface Languages
- Unterstützte Sprachen
+ Weboberfläche Sprachen
- Languages for the user interface. Select at least one.
+ Languages for the user interface. Select at least one (e.g. 'en').
- Sprachen für die Benutzeroberfläche. Mindestens eine auswählen.
+ Sprachen für die Benutzeroberfläche. Mindestens eine angeben (z.B. 'de').
-
+
Default search results/page
@@ -80,7 +89,7 @@
-
+
Number of results that should be display on a single page.
@@ -89,7 +98,7 @@
-
+
Sort series by title
@@ -98,7 +107,7 @@
-
+
Activates alphabetical sorting of series in browsing list and publish form.
@@ -107,6 +116,33 @@
+
+
+ Selectable languages
+
+
+ Auswählbare Sprachen
+
+
+
+
+
+ Languages selectable for a document (ISO 639).
+
+
+ Sprachen, die für ein Dokument auswählbar sind (ISO 639).
+
+
+
+
+
+ Sort alphabetical
+
+
+ Alphabetisch sortieren
+
+
+