From 450224cd1fbb75bc7ba09b8c24bddc39484575ff Mon Sep 17 00:00:00 2001
From: Peter Thienel <thienel@data-quest.de>
Date: Wed, 3 Jan 2024 11:10:24 +0000
Subject: [PATCH] =?UTF-8?q?Resolve=20"MVV:=20Ausgabetemplates=20f=C3=BCr?=
 =?UTF-8?q?=20Objektnamen"?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Closes #3386

Merge request studip/studip!2305
---
 app/controllers/search/studiengaenge.php      |   2 +-
 app/views/search/breadcrumb.php               |   4 +-
 app/views/search/module/_modul.php            |   6 +-
 app/views/search/studiengaenge/einfach.php    |   2 +-
 app/views/search/studiengaenge/mehrfach.php   |   2 +-
 app/views/search/studiengaenge/verlauf.php    |   2 +-
 ....20_tic_3386_templates_for_mvv_objects.php | 122 ++++++++++++++++++
 .../StgteilVersionCondition.class.php         |   2 +-
 lib/models/Abschluss.php                      |  17 ++-
 lib/models/Aufbaustudiengang.php              |   4 +-
 lib/models/Fachbereich.php                    |  18 ++-
 lib/models/Modul.php                          |  29 +++--
 lib/models/ModulLanguage.php                  |   2 +-
 lib/models/ModuleManagementModel.php          |  97 ++++++--------
 lib/models/ModuleManagementModelTreeItem.php  |   7 +-
 lib/models/Modulteil.php                      |  22 +++-
 lib/models/ModulteilLanguage.php              |   2 +-
 lib/models/MvvContact.php                     |   6 +-
 lib/models/MvvContactRange.php                |   4 +-
 lib/models/MvvCourse.php                      |   2 +-
 lib/models/MvvFile.php                        |   6 +-
 lib/models/StgteilVersion.php                 |  59 ++++-----
 lib/models/StgteilabschnittModul.php          |  43 ++----
 lib/models/Studiengang.php                    |  30 ++---
 lib/models/StudiengangTeil.php                |  25 ++--
 lib/models/StudycourseLanguage.php            |   2 +-
 lib/models/StudycourseType.php                |   2 +-
 27 files changed, 317 insertions(+), 202 deletions(-)
 create mode 100644 db/migrations/5.5.20_tic_3386_templates_for_mvv_objects.php

diff --git a/app/controllers/search/studiengaenge.php b/app/controllers/search/studiengaenge.php
index 3987ab52cae..ab4d4de3a77 100644
--- a/app/controllers/search/studiengaenge.php
+++ b/app/controllers/search/studiengaenge.php
@@ -511,7 +511,7 @@ class Search_StudiengaengeController extends MVVController
                 // semester is unknown
                 $options[$version->id] =
                         trim($options[$version->id])
-                        ?: $version->getDisplayName(ModuleManagementModel::DISPLAY_STGTEIL);
+                        ?: $version->getDisplayName();
             }
             $widget->setOptions($options, $this->cur_version_id);
             $widget->setMaxLength(100);
diff --git a/app/views/search/breadcrumb.php b/app/views/search/breadcrumb.php
index 5d74fbc727f..534eb58606b 100644
--- a/app/views/search/breadcrumb.php
+++ b/app/views/search/breadcrumb.php
@@ -11,11 +11,11 @@
             <a href="<?= $link ?>"><?= htmlReady($mvv_object->getDisplayName() . ' (' . $additional_object->name . ')') ?></a>
         <? endif; ?>
         <? if ($mvv_object && $type == 'StgteilBezeichnung' && $additional_object = StudiengangTeil::find($point['add']['StudiengangTeil'])) : ?>
-            <a href="<?= $link ?>"><?= htmlReady($mvv_object->getDisplayName() . ': ' . $additional_object->getDisplayName(ModuleManagementModel::DISPLAY_FACH)) ?></a>
+            <a href="<?= $link ?>"><?= htmlReady($mvv_object->getDisplayName() . ': ' . $additional_object->getDisplayName()) ?></a>
         <? endif; ?>
     <? else : ?>
         <? if ($type == 'StudiengangTeil' && $mvv_object = $type::find($point['id'])) : ?>
-            <a href="<?= $link ?>"><?= htmlReady($mvv_object->getDisplayName(ModuleManagementModel::DISPLAY_FACH)) ?></a>
+            <a href="<?= $link ?>"><?= htmlReady($mvv_object->getDisplayName()) ?></a>
         <? elseif (!empty($point['id']) && $mvv_object = $type::find($point['id'])) : ?>
             <a href="<?= $link ?>"><?= htmlReady($mvv_object->getDisplayName(0)) ?></a>
         <? else : ?>
diff --git a/app/views/search/module/_modul.php b/app/views/search/module/_modul.php
index 1aadce74a19..53c05d87310 100644
--- a/app/views/search/module/_modul.php
+++ b/app/views/search/module/_modul.php
@@ -1,19 +1,19 @@
 <tbody class="<?= $modul_id == $modul->id ? 'not-collapsed' : 'collapsed' ?>">
     <tr class="table-header header-row" id="modul_<?= htmlReady($modul->id) ?>">
         <td style="vertical-align: middle; text-align: center;">
-            <a data-dialog="size=auto" title="<?= htmlReady($modul->getDisplayName(ModuleManagementModel::DISPLAY_CODE | ModuleManagementModel::DISPLAY_SEMESTER)) . ' (' . _('Vollständige Modulbeschreibung') . ')' ?>" href="<?= $controller->link_for('shared/modul/description/' . $modul->id) ?>">
+            <a data-dialog="size=auto" title="<?= htmlReady($modul->getDisplayName()) . ' (' . _('Vollständige Modulbeschreibung') . ')' ?>" href="<?= $controller->link_for('shared/modul/description/' . $modul->id) ?>">
                 <?= Icon::create('log')->asImg(['title' => _('Vollständige Modulbeschreibung')]) ?>
             </a>
         </td>
     <? if (count($modul->getAssignedCoursesBySemester($selected_semester->id, $GLOBALS['user']->id))) : ?>
         <td class="toggle-indicator">
             <a class="mvv-search-modules-row-link mvv-load-in-new-row" href="<?= $controller->action_link("details/{$modul->id}/#{$modul->id}") ?>">
-                <?= htmlReady($modul->getDisplayName(ModuleManagementModel::DISPLAY_CODE)) ?>
+                <?= htmlReady($modul->getDisplayName()) ?>
             </a>
         </td>
     <? else : ?>
         <td class="mvv-search-modules-row">
-            <?= htmlReady($modul->getDisplayName(ModuleManagementModel::DISPLAY_CODE)) ?>
+            <?= htmlReady($modul->getDisplayName()) ?>
         </td>
     <? endif; ?>
         <td class="dont-hide">
diff --git a/app/views/search/studiengaenge/einfach.php b/app/views/search/studiengaenge/einfach.php
index 9250a7ec902..05da9fb0f20 100644
--- a/app/views/search/studiengaenge/einfach.php
+++ b/app/views/search/studiengaenge/einfach.php
@@ -1,5 +1,5 @@
 <?= $this->render_partial('search/breadcrumb') ?>
-<h2><?= htmlReady($studiengang->getDisplayName(ModuleManagementModel::DISPLAY_ABSCHLUSS)) ?></h2>
+<h2><?= htmlReady($studiengang->getDisplayName()) ?></h2>
 <h3><?= _('Ausprägungen') ?></h3>
 <ul class="mvv-result-list">
 <? foreach ($data as $fach_id => $fach) : ?>
diff --git a/app/views/search/studiengaenge/mehrfach.php b/app/views/search/studiengaenge/mehrfach.php
index 78ebd592f40..780b4e4ae3b 100644
--- a/app/views/search/studiengaenge/mehrfach.php
+++ b/app/views/search/studiengaenge/mehrfach.php
@@ -1,7 +1,7 @@
 <?= $this->render_partial('search/breadcrumb') ?>
 <table class="default nohover">
     <caption>
-        <?= _('Studiengang') ?>: <?= htmlReady($studiengang->getDisplayName(ModuleManagementModel::DISPLAY_ABSCHLUSS)) ?>
+        <?= _('Studiengang') ?>: <?= htmlReady($studiengang->getDisplayName()) ?>
         <? if (Config::get()->ENABLE_STUDYCOURSE_INFO_PAGE) : ?>
             <a href="<?= $controller->url_for('search/studiengaenge/info', $studiengang->id)?>" data-dialog>
                 <?= Icon::create('infopage2', Icon::ROLE_CLICKABLE, ['title' => _('Informationen zum Studiengang')]) ?>
diff --git a/app/views/search/studiengaenge/verlauf.php b/app/views/search/studiengaenge/verlauf.php
index 431d94d489f..6eaf9159797 100644
--- a/app/views/search/studiengaenge/verlauf.php
+++ b/app/views/search/studiengaenge/verlauf.php
@@ -8,7 +8,7 @@
             <?= htmlReady($studiengangTeilName) ?>
     <? if ($studiengang && !empty($stgTeilBez)) : ?>
         <h3>
-            <?= sprintf(_('%s im Studiengang %s'), htmlReady($stgTeilBez->getDisplayName()), htmlReady($studiengang->getDisplayName(ModuleManagementModel::DISPLAY_ABSCHLUSS))) ?>
+            <?= sprintf(_('%s im Studiengang %s'), htmlReady($stgTeilBez->getDisplayName()), htmlReady($studiengang->getDisplayName())) ?>
             <? if (Config::get()->ENABLE_STUDYCOURSE_INFO_PAGE) : ?>
                 <a href="<?= $controller->link_for('search/studiengaenge/info', $studiengang->id)?>" data-dialog>
                     <?= Icon::create('infopage2', Icon::ROLE_CLICKABLE, ['title' => _('Informationen zum Studiengang')]) ?>
diff --git a/db/migrations/5.5.20_tic_3386_templates_for_mvv_objects.php b/db/migrations/5.5.20_tic_3386_templates_for_mvv_objects.php
new file mode 100644
index 00000000000..ff3b56f0c32
--- /dev/null
+++ b/db/migrations/5.5.20_tic_3386_templates_for_mvv_objects.php
@@ -0,0 +1,122 @@
+<?php
+
+class Tic3386TemplatesForMvvObjects extends Migration
+{
+    public function description()
+    {
+        return 'Adds configurations for templates to format names of mvv objects.';
+    }
+
+    protected function up()
+    {
+        $db = DBManager::get();
+
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_MODUL', 'string', 'global', '{{module_name}} ({{semester_validity}})', 'mvv',
+                 'Template for modules. Possible placeholders: {{module_code}}, {{module_name}}, {{semester_validity}}',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_MODULTEIL', 'string', 'global', '', 'mvv',
+                 'Template for module parts. Possible placeholders: "
+                    . "{{part_number}}, {{part_number_label}}, {{part_name}}, {{teaching_method}}. "
+                    . "If empty a default name will be displayed.',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_STGTEILVERSION', 'string', 'global', '{{subject_name}} {{credit_points CP}} "
+                    . "{{purpose_addition}}{{, version_ordinal_number}} {{version_type}} {{semester_validity}}', 'mvv',
+                 'Template for versions of study courses. Possible placeholders: "
+                    . "{{subject_name}}, {{credit_points}}, {{purpose_addition}}, {{version_number}}, {{version_type}}, "
+                    . "{{version_ordinal_number}}, {{semester_validity}}.',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_STGTEILABSCHNITTMODUL', 'string', 'global', '{{module_code}} - {{module_name}} ({{semester_validity}})', 'mvv',
+                 'Template for modules displayed in the context of a study course. Possible placeholders: "
+                    . "{{module_code}}, {{module_name}}, {{semester_validity}}. If empty a default name will be displayed.',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_STUDIENGANG', 'string', 'global', '{{study_course_name}} ({{degree_category}})', 'mvv',
+                 'Template for the name of a study course. Possible placeholders: "
+                    . "{{study_course_name}}, {{degree_name}}, {{degree_category}}.',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_STUDIENGANGTEIL', 'string', 'global', '{{subject_name}} {{credit_points}} CP {{purpose_addition}}', 'mvv',
+                 'Template for parts of a study course. Possible placeholders: "
+                    . "{{subject_name}}, {{credit_points}}, {{purpose_addition}}.',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_FACHBEREICH', 'string', 'global', '{{faculty_short_name}} - {{name}}', 'mvv',
+                 'Template for departments. Possible placeholders: {{department_name}}, {{faculty_short_name}}. "
+                    . "Used only if the department is not a faculty. If empty the name of the institution will be displayed.',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+        $db->exec(
+            "INSERT IGNORE INTO `config`
+             (`field`, `type`, `range`, `value`, `section`, `description`, `mkdate`, `chdate`)
+             VALUES
+             (
+                 'MVV_TEMPLATE_NAME_ABSCHLUSS', 'string', 'global', '', 'mvv',
+                 'Template for degrees. Possible placeholders: {{degree_name}}, {{degree_short_name}}. "
+                    . "If empty a default name will be displayed.',
+                 UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+             )"
+        );
+    }
+
+    protected function down()
+    {
+        $query = "DELETE `config`, `config_values`
+                  FROM `config`
+                  LEFT JOIN `config_values` USING (`field`)
+                  WHERE `field` IN (
+                      'MVV_TEMPLATE_NAME_MODUL',
+                      'MVV_TEMPLATE_NAME_MODULTEIL',
+                      'MVV_TEMPLATE_NAME_STGTEILABSCHNITTMODUL',
+                      'MVV_TEMPLATE_NAME_STUDIENGANG',
+                      'MVV_TEMPLATE_NAME_STUDIENGANGTEIL',
+                      'MVV_TEMPLATE_NAME_FACHBEREICH',
+                      'MVV_TEMPLATE_NAME_ABSCHLUSS'
+                  )";
+        DBManger::get()->exec($query);
+    }
+}
diff --git a/lib/classes/admission/userfilter/StgteilVersionCondition.class.php b/lib/classes/admission/userfilter/StgteilVersionCondition.class.php
index 391fba360e2..f6348e50587 100644
--- a/lib/classes/admission/userfilter/StgteilVersionCondition.class.php
+++ b/lib/classes/admission/userfilter/StgteilVersionCondition.class.php
@@ -67,7 +67,7 @@ class StgteilVersionCondition extends UserFilterField
 
         foreach ($this->validValues as $version_id => $name) {
             $stgteilversion = StgteilVersion::find($version_id);
-            $this->validValues[$version_id] = $stgteilversion->getDisplayname();
+            $this->validValues[$version_id] = $stgteilversion->getDisplayName();
         }
     }
 
diff --git a/lib/models/Abschluss.php b/lib/models/Abschluss.php
index 29143423b7c..9f68820931f 100644
--- a/lib/models/Abschluss.php
+++ b/lib/models/Abschluss.php
@@ -362,13 +362,24 @@ class Abschluss extends ModuleManagementModelTreeItem implements PrivacyObject
         return Studiengang::findByAbschluss($this->getId());
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
+        $template = Config::get()->MVV_TEMPLATE_NAME_ABSCHLUSS;
+        if (trim($template)) {
+            $placeholders = [
+                'degree_name',
+                'degree_shortname'
+            ];
+            $replacements = [
+                $this->name,
+                $this->name_kurz
+            ];
+            return self::formatDisplayName($template, $placeholders, $replacements);
+        }
         if ($this->name_kurz != '') {
             return sprintf('%s (%s)', $this->name, $this->name_kurz);
-        } else {
-            return $this->name;
         }
+        return $this->name;
     }
 
     /**
diff --git a/lib/models/Aufbaustudiengang.php b/lib/models/Aufbaustudiengang.php
index 03913de843d..84de9c89d51 100644
--- a/lib/models/Aufbaustudiengang.php
+++ b/lib/models/Aufbaustudiengang.php
@@ -47,9 +47,9 @@ class Aufbaustudiengang extends ModuleManagementModel
         parent::configure($config);
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
-        return $this->aufbau_studiengang->getDisplayName($options);
+        return $this->aufbau_studiengang->getDisplayName();
     }
 
     public function validate()
diff --git a/lib/models/Fachbereich.php b/lib/models/Fachbereich.php
index 7e1586514df..1a8fe0a5684 100644
--- a/lib/models/Fachbereich.php
+++ b/lib/models/Fachbereich.php
@@ -202,14 +202,22 @@ class Fachbereich extends ModuleManagementModelTreeItem
         return count($this->getChildren()) > 0;
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         if ($this->isFaculty()) {
-            return $this->getValue('Name');
+            return $this->name;
         }
-        if ($options & self::DISPLAY_FACULTY) {
-            return (static::findCached($this->fakultaets_id)->getShortName()
-                . ' - ' . $this->name);
+        $template = Config::get()->MVV_TEPLATE_NAME_FACHBEREICH;
+        if (trim($template)) {
+            $placeholders = [
+                'department_name',
+                'faculty_short_name'
+            ];
+            $replacements = [
+                $this->name,
+                static::findCached($this->fakultaets_id)->getShortName()
+            ];
+            return self::formatDisplayName($template, $placeholders, $replacements);
         }
         return ($this->name);
     }
diff --git a/lib/models/Modul.php b/lib/models/Modul.php
index 54764262d81..2d8757c4262 100644
--- a/lib/models/Modul.php
+++ b/lib/models/Modul.php
@@ -332,22 +332,23 @@ class Modul extends ModuleManagementModelTreeItem
         return StgteilabschnittModul::findBySQL('modul_id = ?', [$this->id]);
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT) {
-        $options = ($options !== self::DISPLAY_DEFAULT)
-                ? $options : self::DISPLAY_CODE;
-        $with_code = $options & self::DISPLAY_CODE;
+    public function getDisplayName() {
         if ($this->isNew()) {
-            return parent::getDisplayName($options);
+            return parent::getDisplayName();
         }
-        $name = ($with_code && trim($this->code)) ? $this->code . ' - ' : '';
-        $name .= $this->deskriptoren->bezeichnung;
-        if ($options & self::DISPLAY_SEMESTER) {
-            $sem_validity = $this->getDisplaySemesterValidity();
-            if ($sem_validity) {
-                $name .= ', ' . $sem_validity;
-            }
-        }
-        return trim($name);
+        $template = Config::get()->MVV_TEMPLATE_NAME_MODUL;
+        $placeholders = [
+            'module_code',
+            'module_name',
+            'semester_validity'
+        ];
+        $replacements = [
+            trim($this->code),
+            trim($this->deskriptoren->bezeichnung),
+            $this->getDisplaySemesterValidity()
+        ];
+
+        return self::formatDisplayName($template, $placeholders, $replacements);
     }
 
     /**
diff --git a/lib/models/ModulLanguage.php b/lib/models/ModulLanguage.php
index e8bf1cb0d4a..46d9acbf95a 100644
--- a/lib/models/ModulLanguage.php
+++ b/lib/models/ModulLanguage.php
@@ -65,7 +65,7 @@ class ModulLanguage extends ModuleManagementModel
         return $languages;
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         return $GLOBALS['MVV_MODUL']['SPRACHE']['values'][$this->lang]['name'];
     }
diff --git a/lib/models/ModuleManagementModel.php b/lib/models/ModuleManagementModel.php
index d6b3eabb979..786b1e358f6 100644
--- a/lib/models/ModuleManagementModel.php
+++ b/lib/models/ModuleManagementModel.php
@@ -18,56 +18,6 @@ require_once 'config/mvv_config.php';
 
 abstract class ModuleManagementModel extends SimpleORMap implements ModuleManagementInterface
 {
-    /**
-     * Usable as option for ModuleManagementModel::getDisplayName().
-     * Use the deafault display options for this object.
-     */
-    const DISPLAY_DEFAULT = 1;
-
-    /**
-     * Usable as option for ModuleManagementModel::getDisplayName().
-     * Displays semesters of the validity period if available for this object.
-     */
-    const DISPLAY_SEMESTER = 2;
-
-    /**
-     * Usable as option for ModuleManagementModel::getDisplayName().
-     * Displays the code (usually a unique identifier) if available for this object.
-     */
-    const DISPLAY_CODE = 4;
-
-    /**
-     * Usable as option for ModuleManagementModel::getDisplayName().
-     * Displays the name of the faculty if available for this object.
-     */
-    const DISPLAY_FACULTY = 8;
-
-    /**
-     * Usable as option for ModuleManagementModel::getDisplayName().
-     * Displays the name of the Fach (subject of study) if available for this object.
-     */
-    const DISPLAY_FACH = 16;
-
-    /**
-     * Usable as option for ModuleManagementModel::getDisplayName().
-     * Displays the name of the Studiengangteil if available for this object.
-     */
-    const DISPLAY_STGTEIL = 32;
-
-    /**
-     * Usable as option ModuleManagementModel::getDisplayName().
-     * Displays the name of the Abschluss if available for this object.
-     */
-    const DISPLAY_ABSCHLUSS = 64;
-
-    /**
-     * Usable as option ModuleManagementModel::getDisplayName().
-     * Displays the name of the Abschluss-Kategorie
-     * if available for this object.
-     */
-    const DISPLAY_KATEGORIE = 128;
-
-
     protected static $filter_params = [];
     protected $is_dirty = false;
     private static $language = null;
@@ -546,10 +496,9 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
      * of the mvv objects to display more complex names glued together from
      * fields of related objects.
      *
-     * @param mixed $options An optional parameter to set display options.
-     * @return string The name for
+     * @return string The display name for this object
      */
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         if ($this->isField('name')) {
             return (string) $this->getValue('name');
@@ -817,7 +766,7 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
      * Returns the suffix for ordinal numbers if the selected locale is EN or
      * a simple point if not.
      *
-     * @param type $num
+     * @param int $num
      * @return string The ordinal suffix or a point.
      */
     public static function getLocaleOrdinalNumberSuffix($num)
@@ -966,4 +915,44 @@ abstract class ModuleManagementModel extends SimpleORMap implements ModuleManage
         }
         return static::$object_cache[$index][$id];
     }
+
+    /**
+     * Formats the name of the object by given template and appropriate placeholders and replacements.
+     *
+     * @param string $template The template.
+     * @param array $placeholders All placeholders (words) without regeexp stuff.
+     * @param array $values Values to replace the placeholders. Empty strings will be ignored.
+     * @return string The formatted name.
+     * @throws Exception If the template is not usable.
+     */
+    protected static function formatDisplayName(
+        string $template,
+        array $placeholders, 
+        array $replacements
+    ): string {
+        if (mb_strlen($template) === 0) {
+            return '';
+        }
+        $placeholders = array_map(
+            function ($placeholder) {
+                return '/^(.*?)' . $placeholder . '(.*?)$/';
+            },
+            $placeholders
+        );
+        $replacements = array_map(
+            function ($replacement) {
+                return mb_strlen($replacement) ? '$1' . $replacement . '$2' : '';
+            },
+            $replacements
+        );
+        $markup = new TextFormat();
+        $markup->addMarkup(
+            'mvv',
+            '\{\{', '\}\}',
+            function ($markup, $matches, $content) use ($placeholders, $replacements) {
+                return preg_replace($placeholders, $replacements, $content);
+            }
+        );
+        return $markup->format($template);
+    }
 }
diff --git a/lib/models/ModuleManagementModelTreeItem.php b/lib/models/ModuleManagementModelTreeItem.php
index 522a7b05d40..48c3b1bd8d0 100644
--- a/lib/models/ModuleManagementModelTreeItem.php
+++ b/lib/models/ModuleManagementModelTreeItem.php
@@ -138,14 +138,13 @@ abstract class ModuleManagementModelTreeItem extends ModuleManagementModel imple
      * in class ModuleManagementModel.
      * @return type
      */
-    public static function getPathes($trails, $delimiter = ' · ',
-            $display_options = self::DISPLAY_DEFAULT)
+    public static function getPathes($trails, $delimiter = ' · ')
     {
         $pathes =  [];
         foreach ($trails as $trail) {
             $pathes[] = join($delimiter, array_map(
-                    function($a) use ($display_options) {
-                        return $a->getDisplayName($display_options);
+                    function($a) {
+                        return $a->getDisplayName();
                     }, $trail));
         }
         sort($pathes, SORT_LOCALE_STRING);
diff --git a/lib/models/Modulteil.php b/lib/models/Modulteil.php
index 55ef6fd9140..427610b43c5 100644
--- a/lib/models/Modulteil.php
+++ b/lib/models/Modulteil.php
@@ -164,7 +164,25 @@ class Modulteil extends ModuleManagementModelTreeItem
         );
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT) {
+    public function getDisplayName()
+    {
+        $deskriptor = $this->getDeskriptor(self::getLanguage());
+        $template = Config::get()->MVV_TEMPLATE_NAME_MODULTEIL;
+        if (trim($template)) {
+            $placeholders = [
+                'part_number',
+                'part_number_label',
+                'part_name',
+                'teaching_method'
+            ];
+            $replacements = [
+                $this->nummer,
+                $GLOBALS['MVV_MODULTEIL']['NUM_BEZEICHNUNG']['values'][$this->num_bezeichnung]['name'] ?? '',
+                trim($deskriptor->bezeichnung),
+                $GLOBALS['MVV_MODULTEIL']['LERNLEHRFORM']['values'][$this->lernlehrform]['name'] ?? ''
+            ];
+            return self::formatDisplayName($template, $placeholders, $replacements);
+        }
         $name = '';
         if ($this->num_bezeichnung) {
             $name .= $GLOBALS['MVV_MODULTEIL']['NUM_BEZEICHNUNG']['values'][$this->num_bezeichnung]['name'];
@@ -174,12 +192,10 @@ class Modulteil extends ModuleManagementModelTreeItem
             $name .= $this->nummer . ': ';
         }
         $name .= $GLOBALS['MVV_MODULTEIL']['LERNLEHRFORM']['values'][$this->lernlehrform]['name'] ?? '';
-        $deskriptor = $this->getDeskriptor(self::getLanguage());
         if (strlen(trim($deskriptor->bezeichnung))) {
             $name .= $name == '' ? $deskriptor->bezeichnung
                     : ' (' . $deskriptor->bezeichnung . ')';
         }
-
         return trim($name);
     }
 
diff --git a/lib/models/ModulteilLanguage.php b/lib/models/ModulteilLanguage.php
index af510c5f0db..a37a4906a4b 100644
--- a/lib/models/ModulteilLanguage.php
+++ b/lib/models/ModulteilLanguage.php
@@ -66,7 +66,7 @@ class ModulteilLanguage extends ModuleManagementModel
         return $languages;
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         return $GLOBALS['MVV_MODULTEIL']['SPRACHE']['values'][$this->lang]['name'];
     }
diff --git a/lib/models/MvvContact.php b/lib/models/MvvContact.php
index 3aa72d65546..a174bd68c63 100644
--- a/lib/models/MvvContact.php
+++ b/lib/models/MvvContact.php
@@ -49,11 +49,11 @@ class MvvContact extends ModuleManagementModel
     }
 
     /**
-     * Returns the name of the object to display in a specific context..
+     * Returns the name of the object to display in a specific context.
      *
-     * @return string The name for
+     * @return string The name.
      */
-    public function getDisplayName($options = null)
+    public function getDisplayName()
     {
         return $this->name;
     }
diff --git a/lib/models/MvvContactRange.php b/lib/models/MvvContactRange.php
index 8881e59118f..4b3bcfcc267 100644
--- a/lib/models/MvvContactRange.php
+++ b/lib/models/MvvContactRange.php
@@ -55,9 +55,9 @@ class MvvContactRange extends ModuleManagementModel
     /**
      * Returns the name of the object to display in a specific context..
      *
-     * @return string The name for
+     * @return string The name.
      */
-    public function getDisplayName($options = null)
+    public function getDisplayName()
     {
         return $this->contact->name;
     }
diff --git a/lib/models/MvvCourse.php b/lib/models/MvvCourse.php
index 78b6aeca0d4..4d7c3801757 100644
--- a/lib/models/MvvCourse.php
+++ b/lib/models/MvvCourse.php
@@ -93,7 +93,7 @@ class MvvCourse extends ModuleManagementModelTreeItem
         return false;
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         return $this->name;
     }
diff --git a/lib/models/MvvFile.php b/lib/models/MvvFile.php
index 67b8b5fbc25..f4f4324c2e5 100644
--- a/lib/models/MvvFile.php
+++ b/lib/models/MvvFile.php
@@ -73,11 +73,11 @@ class MvvFile extends ModuleManagementModel
     }
 
     /**
-     * Returns the name of the object to display in a specific context..
+     * Returns the name of the object to display in a specific context.
      *
-     * @return string The name for
+     * @return string The name.
      */
-    public function getDisplayName($options = null)
+    public function getDisplayName()
     {
         if ($this->file_refs) {
             return $this->file_refs[0]->name;
diff --git a/lib/models/StgteilVersion.php b/lib/models/StgteilVersion.php
index b2b56e262c7..5add60b5ca9 100644
--- a/lib/models/StgteilVersion.php
+++ b/lib/models/StgteilVersion.php
@@ -280,44 +280,31 @@ class StgteilVersion extends ModuleManagementModelTreeItem
                 });
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         if ($this->isNew()) {
             return '';
         }
-
-        $options = ($options !== self::DISPLAY_DEFAULT)
-                ? $options : (self::DISPLAY_STGTEIL | self::DISPLAY_FACH);
-        $with_stgteil = $options & self::DISPLAY_STGTEIL;
-        $with_fach = $options & self::DISPLAY_FACH;
-        $start_sem = Semester::find($this->start_sem);
-        $end_sem = Semester::find($this->end_sem);
-        $fassung_nr = $this->fassung_nr
-            ? $this->fassung_nr . ModuleManagementModel::getLocaleOrdinalNumberSuffix($this->fassung_nr). ' '
-            : '';
-        $fassung_typ = $this->fassung_typ
-            ? $GLOBALS['MVV_STGTEILVERSION']['FASSUNG_TYP'][$this->fassung_typ]['name'] . ' '
-            : '';
-        if (!$end_sem) {
-            if (!$start_sem) {
-                $name = $fassung_nr . trim($fassung_typ);
-            } else {
-                $name = $fassung_nr . $fassung_typ . sprintf(_('gültig ab %s'), $start_sem->name);
-            }
-        } else {
-            if ($start_sem->name == $end_sem->name) {
-                $name = $fassung_nr . $fassung_typ . '(' . $start_sem->name . ')';
-            } else {
-                $name = $fassung_nr . $fassung_typ
-                        . sprintf('(%s - %s)', $start_sem->name, $end_sem->name);
-            }
-        }
-        if ($with_stgteil) {
-            return $this->studiengangteil->getDisplayName($with_fach)
-                    . (trim($name) ? ', ' . $name : '');
-        } else {
-            return $name;
-        }
+        $template = Config::get()->MVV_TEMPLATE_NAME_STGTEILVERSION;
+        $placeholders = [
+            'version_number',
+            'version_ordinal_number',
+            'version_type',
+            'subject_name',
+            'semester_validity',
+            'credit_points',
+            'purpose_addition'
+        ];
+        $replacements = [
+            $this->fassung_nr,
+            $this->fassung_nr . ModuleManagementModel::getLocaleOrdinalNumberSuffix($this->fassung_nr),
+            $this->fassung_typ ? $GLOBALS['MVV_STGTEILVERSION']['FASSUNG_TYP'][$this->fassung_typ]['name'] : '',
+            $this->studiengangteil->fach->name,
+            $this->getDisplaySemesterValidity(),
+            trim($this->studiengangteil->kp),
+            trim($this->studiengangteil->zusatz)
+        ];
+        return $this->formatDisplayName($template, $placeholders, $replacements);
     }
 
     /**
@@ -333,9 +320,9 @@ class StgteilVersion extends ModuleManagementModelTreeItem
         if ($end_sem || $start_sem) {
             if ($end_sem) {
                 if ($start_sem->name == $end_sem->name) {
-                    $ret .= sprintf(_('gültig im %s'), $start_sem->name);
+                    $ret .= sprintf(_('%s'), $start_sem->name);
                 } else {
-                    $ret .= sprintf(_('gültig %s bis %s'), $start_sem->name, $end_sem->name);
+                    $ret .= sprintf(_('%s - %s'), $start_sem->name, $end_sem->name);
                 }
             } else {
                 $ret .= sprintf(_('gültig ab %s'), $start_sem->name);
diff --git a/lib/models/StgteilabschnittModul.php b/lib/models/StgteilabschnittModul.php
index 6b2f01f7c90..d871df6b4ab 100644
--- a/lib/models/StgteilabschnittModul.php
+++ b/lib/models/StgteilabschnittModul.php
@@ -68,38 +68,23 @@ class StgteilabschnittModul extends ModuleManagementModelTreeItem
             : _('Modul'));
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
-        $options = ($options !== self::DISPLAY_DEFAULT)
-                ? $options : self::DISPLAY_CODE;
-        $with_code = $options & self::DISPLAY_CODE;
         if ($this->isNew()) {
-            return parent::getDisplayName($options);
+            return parent::getDisplayName();
         }
-
-        /* Augsburg
-        return ($this->bezeichnung ? $this->bezeichnung . ': ' : '')
-            . $this->getModul()->getDisplayName();
-         *
-         */
-
-        $start_sem = Semester::find($this->modul->start);
-        $end_sem = Semester::find($this->modul->end);
-
-        $code = trim($this->modulcode) ?: trim($this->modul->code);
-
-        $name = ($with_code && $code) ? $code . ' - ' : '';
-        $name .= trim($this->bezeichnung) ?: trim($this->modul->getDeskriptor()->bezeichnung);
-        if ($end_sem || $start_sem) {
-            if ($end_sem) {
-                $name .= sprintf(_(', gültig %s bis %s'),
-                        $start_sem->name, $end_sem->name);
-            } else {
-                $name .= sprintf(_(', gültig ab %s'), $start_sem->name);
-            }
-        }
-
-        return $name;
+        $template = Config::get()->MVV_TEMPLATE_NAME_STGTEILABSCHNITTMODUL;
+        $placeholders = [
+            'module_code',
+            'module_name',
+            'semester_validity'
+        ];
+        $replacements = [
+            trim($this->modulcode) ?: trim($this->modul->code),
+           trim($this->bezeichnung) ?: trim($this->modul->getDeskriptor()->bezeichnung),
+            $this->modul->getDisplaySemesterValidity()
+        ];
+        return self::formatDisplayName($template, $placeholders, $replacements);
     }
 
     /**
diff --git a/lib/models/Studiengang.php b/lib/models/Studiengang.php
index 9c9be779b70..ee89abde0e1 100644
--- a/lib/models/Studiengang.php
+++ b/lib/models/Studiengang.php
@@ -508,24 +508,20 @@ class Studiengang extends ModuleManagementModelTreeItem
         return self::get();
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
-        if ($options == self::DISPLAY_DEFAULT) {
-            $options = self::DISPLAY_KATEGORIE;
-        }
-
-        $ret = $this->name;
-        if ($options & self::DISPLAY_ABSCHLUSS) {
-            $ret .= ' (' . $this->abschluss->name . ')';
-        }
-        if ($options & self::DISPLAY_KATEGORIE) {
-            $ret .= (mb_strlen($this->abschluss->category->name)
-                ? ' (' . $this->abschluss->category->name . ')'
-                : ''
-            );
-        }
-
-        return $ret;
+        $template = Config::get()->MVV_TEMPLATE_NAME_STUDIENGANG;
+        $placeholders = [
+            'study_course_name',
+            'degree_name',
+            'degree_category'
+        ];
+        $replacements = [
+            $this->name,
+            $this->abschluss->name,
+            $this->abschluss->category->name
+        ];
+        return self::formatDisplayName($template, $placeholders, $replacements);
     }
 
     /**
diff --git a/lib/models/StudiengangTeil.php b/lib/models/StudiengangTeil.php
index 9292dd9b1b9..13bf7ce37a4 100644
--- a/lib/models/StudiengangTeil.php
+++ b/lib/models/StudiengangTeil.php
@@ -122,22 +122,23 @@ class StudiengangTeil extends ModuleManagementModelTreeItem
         return false;
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
-        $options = $options !== self::DISPLAY_DEFAULT
-                ? $options : self::DISPLAY_FACH;
-        $with_fach = $options & self::DISPLAY_FACH;
         if ($this->isNew()) {
             return '';
         }
-        if ($this->fach) {
-            $name = $with_fach ? $this->fach->name . ' ' : '';
-        } else {
-            $name = '';
-        }
-        $name .= $this->kp ?  $this->kp . ' CP ' : '';
-        $name .= $this->zusatz ? $this->zusatz  : '';
-        return trim($name);
+        $template = Config::get()->MVV_TEMPLATE_NAME_STUDIENGANGTEIL;
+        $placeholders = [
+            'subject_name',
+            'credit_points',
+            'purpose_addition'
+        ];
+        $replacements = [
+            $this->fach->name,
+            trim($this->kp),
+            trim($this->zusatz)
+        ];
+        return self::formatDisplayName($template, $placeholders, $replacements);
     }
 
     /**
diff --git a/lib/models/StudycourseLanguage.php b/lib/models/StudycourseLanguage.php
index 3e1a8606112..6e705263c48 100644
--- a/lib/models/StudycourseLanguage.php
+++ b/lib/models/StudycourseLanguage.php
@@ -45,7 +45,7 @@ class StudycourseLanguage extends ModuleManagementModel
         parent::configure($config);
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         return $GLOBALS['MVV_STUDIENGANG']['SPRACHE']['values'][$this->lang]['name'];
     }
diff --git a/lib/models/StudycourseType.php b/lib/models/StudycourseType.php
index 07367eb5b22..3c9d5a8539c 100644
--- a/lib/models/StudycourseType.php
+++ b/lib/models/StudycourseType.php
@@ -39,7 +39,7 @@ class StudycourseType extends ModuleManagementModel
         parent::configure($config);
     }
 
-    public function getDisplayName($options = self::DISPLAY_DEFAULT)
+    public function getDisplayName()
     {
         return $GLOBALS['MVV_STUDIENGANG']['STUDYCOURSE_TYPE']['values'][$this->type]['name'];
     }
-- 
GitLab