From 7dddea8ccca601bf2da28960f2e27a223fe60ea6 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Thu, 24 Nov 2022 10:29:24 +0000
Subject: [PATCH] rework aux lock rules, use sorm model, deprecate old class
 and let name and description be translatable, fixes #1791

Closes #1791

Merge request studip/studip!1177
---
 app/controllers/admin/courses.php             |   8 +-
 app/controllers/admin/specification.php       | 106 +++++++++-----
 app/controllers/authenticated_controller.php  |  21 ++-
 .../consultation/consultation_controller.php  |  12 --
 app/controllers/course/members.php            |   3 +
 app/controllers/course/overview.php           |  26 ++--
 app/views/admin/courses/aux-select.php        |  12 +-
 app/views/admin/courses/aux_preselect.php     |  21 +--
 app/views/admin/specification/_field.php      |  28 ++--
 app/views/admin/specification/edit.php        | 129 ++++++++---------
 app/views/admin/specification/index.php       |  75 +++++-----
 app/views/course/members/additional_input.php |  54 ++++---
 ...3.9_convert_aux_lock_rules_json_fields.php |  56 ++++++++
 lib/classes/AuxLockRules.class.php            |   3 +
 lib/classes/Markup.class.php                  |   2 +-
 lib/classes/StudipArrayObject.class.php       |   8 ++
 lib/classes/WidgetContainer.php               |   5 +-
 lib/models/AuxLockRule.php                    | 133 +++++++++++++-----
 lib/models/DatafieldEntryModel.class.php      |  82 ++++++-----
 templates/i18n/input.php                      |  10 ++
 20 files changed, 481 insertions(+), 313 deletions(-)
 create mode 100644 db/migrations/5.3.9_convert_aux_lock_rules_json_fields.php

diff --git a/app/controllers/admin/courses.php b/app/controllers/admin/courses.php
index e832628003e..7e78aaea4d5 100644
--- a/app/controllers/admin/courses.php
+++ b/app/controllers/admin/courses.php
@@ -372,13 +372,7 @@ class Admin_CoursesController extends AuthenticatedController
             ]],
             LockRule::findAllByType('sem')
         ));
-        $this->aux_lock_rules = array_merge(
-            [[
-                'name'    => '--' . _("keine Zusatzangaben") . '--',
-                'lock_id' => 'none'
-            ]],
-            AuxLockRules::getAllLockRules()
-        );
+        $this->aux_lock_rules = AuxLockRule::findBySQL('1 ORDER BY name');
 
 
         //build the sidebar:
diff --git a/app/controllers/admin/specification.php b/app/controllers/admin/specification.php
index 977ec1830f5..819d59bd82d 100644
--- a/app/controllers/admin/specification.php
+++ b/app/controllers/admin/specification.php
@@ -17,6 +17,8 @@
  */
 class Admin_SpecificationController extends AuthenticatedController
 {
+    protected $_autobind = true;
+
     /**
      * Common tasks for all actions.
      */
@@ -41,25 +43,39 @@ class Admin_SpecificationController extends AuthenticatedController
      */
     public function index_action()
     {
-        $this->allrules = AuxLockRules::getAllLockRules();
+        $this->rules = AuxLockRule::findBySQL('1 ORDER BY name');
+
+        Sidebar::Get()->addWidget(new ActionsWidget())->addLink(
+            _('Neue Regel anlegen'),
+            $this->editURL(),
+            Icon::create('add')
+        );
     }
 
     /**
      * Edit or create a rule
-     *
-     * @param string $edit_id
+     * @property AuxLockRule $rule
      */
-    public function edit_action($id = null)
+    public function edit_action(AuxLockRule $rule = null)
     {
-        //get data
-        $user_field            = 'user';
-        $semdata_field         = 'usersemdata';
-        $this->semFields       = AuxLockRules::getSemFields();
-        $this->entries_user    = DataField::getDataFields($user_field);
-        $this->entries_semdata = DataField::getDataFields($semdata_field);
-        $this->rule            = is_null($id) ? false : AuxLockRules::getLockRuleByID($id);
-
-        if ($GLOBALS['perm']->have_perm('root') && count($this->entries_semdata) == 0) {
+        $rule->name = Request::i18n('name', $rule->name);
+        $rule->description = Request::i18n('description', $rule->description);
+        $rule->attributes = Request::optionArray('fields') ?: $rule->attributes;
+        $rule->sorting = Request::getArray('order') ?: $rule->sorting;
+
+        if ($GLOBALS['perm']->have_perm('root')) {
+            Sidebar::Get()->addWidget(new ActionsWidget())->addLink(
+                _('Datenfelder bearbeiten'),
+                URLHelper::getURL('dispatch.php/admin/datafields'),
+                Icon::create('edit')
+            );
+        }
+
+        $this->semFields       = $this->getSemFields();
+        $this->entries_user    = DataField::getDataFields('user');
+        $this->entries_semdata = DataField::getDataFields('usersemdata');
+
+        if ($GLOBALS['perm']->have_perm('root') && count($this->entries_semdata) === 0) {
             PageLayout::postWarning(sprintf(
                 _('Sie müssen zuerst im Bereich %sDatenfelder%s in der Kategorie '
                 . '<em>Datenfelder für Personenzusatzangaben in Veranstaltungen</em> '
@@ -74,51 +90,63 @@ class Admin_SpecificationController extends AuthenticatedController
      * Store or edit Rule
      * @param string $id
      */
-    public function store_action($id = '')
+    public function store_action(AuxLockRule $rule = null)
     {
-        CSRFProtection::verifyRequest();
+        CSRFProtection::verifyUnsafeRequest();
 
         $errors = [];
-        if (!Request::get('rulename')) {
+        if (!trim(Request::get('name'))) {
             $errors[] = _('Bitte geben Sie der Regel mindestens einen Namen!');
         }
-        if (!AuxLockRules::checkLockRule(Request::getArray('fields'))) {
+
+        if (!AuxLockRule::validateFields(Request::optionArray('fields'))) {
             $errors[] = _('Bitte wählen Sie mindestens ein Feld aus der Kategorie "Zusatzinformationen" aus!');
         }
 
-        if (empty($errors)) {
-            if (!$id) {
-                //new
-                AuxLockRules::createLockRule(Request::get('rulename'), Request::get('description'), Request::getArray('fields'), Request::getArray('order'));
-            } else {
-                //edit
-                AuxLockRules::updateLockRule($id, Request::get('rulename'), Request::get('description'), Request::getArray('fields'), Request::getArray('order'));
-            }
-            PageLayout::postSuccess(sprintf(
-                _('Die Regel "%s" wurde erfolgreich gespeichert!'),
-                htmlReady(Request::get('rulename'))
-            ));
-        } else {
+        if ($errors) {
             PageLayout::postError(_('Ihre Eingaben sind ungültig.'), $errors);
-        }
+            $this->keepRequest();
+            $this->redirect($this->editURL($rule));
+        } else {
+            $rule->name = Request::i18n('name');
+            $rule->description = Studip\Markup::purifyHtml(Request::i18n('description'));
+            $rule->attributes = Request::optionArray('fields') ?? [];
+            $rule->sorting = Request::getArray('order') ?? [];
 
-        $this->redirect('admin/specification');
+            if ($rule->store()) {
+                PageLayout::postSuccess(sprintf(
+                    _('Die Regel "%s" wurde erfolgreich gespeichert!'),
+                    htmlReady($rule->name)
+                ));
+            }
+            $this->redirect('admin/specification');
+        }
     }
 
     /**
      * Delete a rule, using a modal dialog
-     *
-     * @param string $rule_id
      */
-    public function delete_action($rule_id)
+    public function delete_action(AuxLockRule $rule)
     {
         CSRFProtection::verifyUnsafeRequest();
-        if (AuxLockRules::deleteLockRule($rule_id)) {
-            PageLayout::postSuccess(_('Die Regel wurde erfolgreich gelöscht!'));
-        } else {
+
+        $result = $rule->delete();
+        if ($result === false) {
             PageLayout::postError(_('Es können nur nicht verwendete Regeln gelöscht werden!'));
+        } elseif ($result > 0) {
+            PageLayout::postSuccess(_('Die Regel wurde erfolgreich gelöscht!'));
         }
 
-        $this->redirect('admin/specification');
+        $this->redirect($this->indexURL());
+    }
+
+    private function getSemFields(): array
+    {
+        return [
+            'vasemester' => _('Semester'),
+            'vanr'       => _('Veranstaltungsnummer'),
+            'vatitle'    => _('Veranstaltungstitel'),
+            'vadozent'   => _('Dozent'),
+        ];
     }
 }
diff --git a/app/controllers/authenticated_controller.php b/app/controllers/authenticated_controller.php
index f50d478c9fa..e051ffa7156 100644
--- a/app/controllers/authenticated_controller.php
+++ b/app/controllers/authenticated_controller.php
@@ -1,8 +1,4 @@
 <?php
-# Lifter007: TODO
-# Lifter003: TODO
-# Lifter010: TODO
-
 /*
  * Copyright (C) 2009 - Marcus Lunzenauer <mlunzena@uos.de>
  *
@@ -16,4 +12,21 @@ class AuthenticatedController extends StudipController
 {
     protected $with_session = true;  //we do need to have a session for this controller
     protected $allow_nobody = false; //nobody is not allowed and always gets a login-screen
+
+    public function before_filter(&$action, &$args)
+    {
+        parent::before_filter($action, $args);
+
+        // Restore request if present
+        if (isset($this->flash['request'])) {
+            foreach ($this->flash['request'] as $key => $value) {
+                Request::set($key, $value);
+            }
+        }
+    }
+
+    protected function keepRequest()
+    {
+        $this->flash['request'] = Request::getInstance()->getIterator()->getArrayCopy();
+    }
 }
diff --git a/app/controllers/consultation/consultation_controller.php b/app/controllers/consultation/consultation_controller.php
index eba81ff4967..dc7fd6e6a90 100644
--- a/app/controllers/consultation/consultation_controller.php
+++ b/app/controllers/consultation/consultation_controller.php
@@ -26,13 +26,6 @@ abstract class ConsultationController extends AuthenticatedController
             URLHelper::addLinkParam('cid', $this->range->id);
         }
 
-        // Restore request if present
-        if (isset($this->flash['request'])) {
-            foreach ($this->flash['request'] as $key => $value) {
-                Request::set($key, $value);
-            }
-        }
-
         // This defines the function to display a note. Not really a partial,
         // not a controller method. This has no real place...
         $this->displayNote = function ($what, $length = 40, $position = 'above') {
@@ -72,11 +65,6 @@ abstract class ConsultationController extends AuthenticatedController
         return $this->range->getConfiguration()->CONSULTATION_TAB_TITLE;
     }
 
-    protected function keepRequest()
-    {
-        $this->flash['request'] = Request::getInstance()->getIterator()->getArrayCopy();
-    }
-
     /**
      * @param $block_id
      *
diff --git a/app/controllers/course/members.php b/app/controllers/course/members.php
index 16c59a3ed27..ad7d6dacf93 100644
--- a/app/controllers/course/members.php
+++ b/app/controllers/course/members.php
@@ -1330,6 +1330,9 @@ class Course_MembersController extends AuthenticatedController
         $course = Course::findCurrent();
         $member = $course->members->findOneBy('user_id', $GLOBALS['user']->id);
         $this->datafields = $member ? $course->aux->getMemberData($member) : [];
+
+        $this->editable = false;
+
         // We need aux data in the view
         $this->aux = $course->aux;
 
diff --git a/app/controllers/course/overview.php b/app/controllers/course/overview.php
index 0994d547374..96e7f5abefc 100644
--- a/app/controllers/course/overview.php
+++ b/app/controllers/course/overview.php
@@ -87,24 +87,14 @@ class Course_OverviewController extends AuthenticatedController
             $this->show_dozenten = $show_dozenten;
 
             // Check lock rules
-            if (!$GLOBALS["perm"]->have_studip_perm('dozent', $this->course_id)) {
-                $rule = AuxLockRules::getLockRuleBySemId($this->course_id);
-                if (isset($rule)) {
-                    $show = false;
-                    foreach ((array) $rule['attributes'] as $val) {
-                        if ($val == 1) {
-                            // Es gibt also Zusatzangaben. Nun noch überprüfen ob der Nutzer diese Angaben schon gemacht hat...
-                            $count = DataField::countBySql("LEFT JOIN datafields_entries USING (datafield_id) WHERE object_type = ? AND sec_range_id = ? AND range_id = ?",
-                                ['usersemdata', $this->course_id, $GLOBALS['user']->id]
-                            );
-                            if (!$count) {
-                                $show = true;
-                            }
-                            break;
-                        }
-                    }
-
-                    if ($show) {
+            if (!$GLOBALS['perm']->have_studip_perm('dozent', $this->course_id)) {
+                $rule = AuxLockRule::findOneByCourse($this->course);
+                if ($rule && count($rule->attributes) > 0) {
+                    $count = DataField::countBySql("LEFT JOIN datafields_entries USING (datafield_id) WHERE object_type = ? AND sec_range_id = ? AND range_id = ?",
+                        ['usersemdata', $this->course_id, $GLOBALS['user']->id]
+                    );
+
+                    if ($count === 0) {
                         PageLayout::postInfo(
                             _("Sie haben noch nicht die für diese Veranstaltung benötigten Zusatzinformationen eingetragen."),
                             [
diff --git a/app/views/admin/courses/aux-select.php b/app/views/admin/courses/aux-select.php
index 1786598a0b2..950120e081f 100644
--- a/app/views/admin/courses/aux-select.php
+++ b/app/views/admin/courses/aux-select.php
@@ -1,13 +1,17 @@
 <?php
 /**
  * @var Course $course
- * @var array $aux_lock_rules
+ * @var AuxLockRule[] $aux_lock_rules
+ * @var array $values
  */
 ?>
 <select name="lock_sem[<?= htmlReady($course->id) ?>]" style="max-width: 200px">
-<? foreach ($aux_lock_rules as $id => $rule) : ?>
-    <option value="<?= $id ?>" <?= $values['aux_lock_rule'] == $id ?  'selected' : '' ?>>
-        <?= htmlReady($rule['name']) ?>
+    <option value="none">
+        --<?= _('keine Zusatzangaben') ?>--
+    </option>
+<? foreach ($aux_lock_rules as $rule) : ?>
+    <option value="<?= htmlReady($rule->id) ?>" <? if ($values['aux_lock_rule'] === $rule->id) echo 'selected'; ?>>
+        <?= htmlReady($rule->name) ?>
     </option>
 <? endforeach ?>
 </select>
diff --git a/app/views/admin/courses/aux_preselect.php b/app/views/admin/courses/aux_preselect.php
index e44e378973f..73a2036577e 100644
--- a/app/views/admin/courses/aux_preselect.php
+++ b/app/views/admin/courses/aux_preselect.php
@@ -1,21 +1,24 @@
 <?php
 /**
  * @var array $values
- * @var array $aux_lock_rules
+ * @var AuxLockRule[] $aux_lock_rules
  */
 ?>
 <label><?= _('Für alle Veranstaltungen') ?>
     <select name="lock_sem_all" style="max-width: 200px">
-        <? foreach ($aux_lock_rules as $id => $rule) : ?>
-            <option value="<?= $id ?>"
-                <?= ($values['aux_lock_rule'] == $id) ? 'selected' : '' ?>>
-                <?= htmlReady($rule["name"]) ?>
-            </option>
-        <? endforeach ?>
+        <option value="none">
+            --<?= _('keine Zusatzangaben') ?>--
+        </option>
+    <? foreach ($aux_lock_rules as $rule) : ?>
+        <option value="<?= htmlReady($rule->id) ?>"
+            <? if ($values['aux_lock_rule'] === $rule->id) echo 'selected'; ?>>
+            <?= htmlReady($rule->name) ?>
+        </option>
+    <? endforeach ?>
     </select>
 </label>
 <label>
-<input type="checkbox" value="1" name="aux_all_forced">
-<?=_("Erzwungen")?>
+    <input type="checkbox" value="1" name="aux_all_forced">
+    <?=_('Erzwungen')?>
 </label>
 <?= \Studip\Button::createAccept(_('Speichern'), 'all'); ?>
diff --git a/app/views/admin/specification/_field.php b/app/views/admin/specification/_field.php
index 05ea09f6073..3d2703b7571 100644
--- a/app/views/admin/specification/_field.php
+++ b/app/views/admin/specification/_field.php
@@ -2,40 +2,36 @@
 /**
  * @var string $name
  * @var string $id
- * @var array $rule
+ * @var AuxLockRule $rule
  */
-$fields = Request::getArray('fields');
-$order  = Request::getArray('order');
 ?>
 
 <section>
-    <? if (!empty($required)) : ?>
+<? if (!empty($required)) : ?>
     <span class="required">
         <?= htmlReady($name) ?>
     </span>
-    <? else : ?>
+<? else: ?>
     <?= htmlReady($name) ?>
-    <? endif ?>
-
+<? endif ?>
 
     <div class="hgroup">
         <label class="col-2">
             <?= _('Sortierung') ?>
-            <input id="order_<?= $id ?>" min="0" type="number" size="3" name="order[<?= $id ?>]"
-                   value="<?= (int)(($order && isset($order[$id])) ? $order[$id] : @$rule['order'][$id]) ?>">
-            <input type="hidden" name="fields[<?= $id ?>]" value="0">
+            <input id="order_<?= htmlReady($id) ?>" min="0" type="number" size="3" name="order[<?= htmlReady($id) ?>]"
+                   value="<?= ($rule->sorting[$id] ?? 0) ?>">
         </label>
         <label class="col-2">
             <input type="checkbox"
-                   name="fields[<?= $id ?>]"
-                   value="1"
-                    <?= (($fields && isset($fields[$id])) ? $fields[$id] : @$rule['attributes'][$id]) ? 'checked="checked"' : '' ?>>
+                   name="fields[]"
+                   value="<?= htmlReady($id) ?>"
+                    <? if ($rule->attributes->contains($id)) echo 'checked'; ?>>
             <?= _('Aktivieren') ?>
         </label>
+    <? if (!empty($institution)) : ?>
         <label class="col-1">
-            <? if (!empty($institution)) : ?>
-                <?= htmlReady($institution->name)?>
-            <? endif; ?>
+            <?= htmlReady($institution->name )?>
         </label>
+    <? endif; ?>
     </div>
 </section>
diff --git a/app/views/admin/specification/edit.php b/app/views/admin/specification/edit.php
index 30bee490d5d..452c39b8e2c 100644
--- a/app/views/admin/specification/edit.php
+++ b/app/views/admin/specification/edit.php
@@ -3,95 +3,86 @@
 
 /**
  * @var Admin_SpecificationController $controller
- * @var AuxLockRules $rule
+ * @var AuxLockRule $rule
  * @var array $semFields
  * @var DataField[] $entries_semdata
  * @var DataField[] $entries_user
  */
 use Studip\Button, Studip\LinkButton;
 ?>
-<? if (isset($flash['error'])) : ?>
-    <?= MessageBox::error($flash['error'], $flash['error_detail']) ?>
-<? elseif (isset($flash['info'])): ?>
-    <?= MessageBox::info($flash['info']) ?>
-<? endif ?>
-
-<form action="<?= $controller->url_for('admin/specification/store' . ($rule ? '/' . $rule['lock_id'] : '')) ?>"
-      method="post" class="default">
+<form action="<?= $controller->store($rule) ?>" method="post" class="default">
     <?= CSRFProtection::tokenTag() ?>
     <fieldset>
         <legend>
-            <? if ($rule) : ?>
-                <?= sprintf(_('Regel "%s" editieren'), htmlReady($rule['name'])) ?>
-            <? else : ?>
-                <?= _('Eine neue Regel definieren') ?>
-            <? endif ?>
+        <? if ($rule->isNew()) : ?>
+            <?= _('Eine neue Regel definieren') ?>
+        <? else : ?>
+            <?= sprintf(_('Regel "%s" editieren'), htmlReady($rule['name'])) ?>
+        <? endif ?>
         </legend>
         <label>
             <span class="required">
-                <?= _('Name der Regel:') ?>
+                <?= _('Name der Regel') ?>
             </span>
-            <input type="text" name="rulename" value="<?= htmlReady(Request::get('rulename', $rule ? $rule['name'] : '')) ?>"
-                   required="required">
+            <?= I18N::input('name', $rule->name, [
+                'required' => '',
+            ]) ?>
         </label>
         <label>
             <?= _('Beschreibung') ?>
-            <textarea cols="60" rows="5"
-                      name="description"><?= htmlReady(Request::get('description', $rule ? $rule['description'] : '')) ?></textarea>
+            <?= I18N::textarea('description', $rule->description, [
+                'class' => 'wysiwyg',
+            ]) ?>
         </label>
     </fieldset>
-    <? if (count($entries_semdata) > 0) : ?>
-        <fieldset>
-            <legend>
-                <?= _('Zusatzinformationen') ?>
-            </legend>
-            <? foreach ($entries_semdata as $id => $entry) : ?>
-                <?= $this->render_partial('admin/specification/_field', array_merge(
-                    compact('rule'),
-                    ['id' => $entry->datafield_id, 'name' => $entry->name],
-                    ['required' => true, 'institution' => $entry->institution]
-                )) ?>
-            <? endforeach ?>
-        </fieldset>
-    <? endif ?>
 
-    <? if (count($semFields) > 0) : ?>
-        <fieldset>
-            <legend>
-                <?= _('Veranstaltungsinformationen') ?>
-            </legend>
-            <? foreach ($semFields as $id => $name) : ?>
-                <?= $this->render_partial('admin/specification/_field', compact('rule', 'id', 'name')) ?>
-            <? endforeach ?>
-        </fieldset>
-    <? endif ?>
+<? if (count($entries_semdata) > 0) : ?>
+    <fieldset>
+        <legend>
+            <?= _('Zusatzinformationen') ?>
+        </legend>
+    <? foreach ($entries_semdata as $id => $entry) : ?>
+        <?= $this->render_partial('admin/specification/_field', [
+            'rule'        => $rule,
+            'id'          => $entry->datafield_id,
+            'name'        => $entry->name,
+            'required'    => true,
+            'institution' => $entry->institution,
+        ]) ?>
+    <? endforeach ?>
+    </fieldset>
+<? endif ?>
+
+    <fieldset>
+        <legend>
+            <?= _('Veranstaltungsinformationen') ?>
+        </legend>
+    <? foreach ($semFields as $id => $name) : ?>
+        <?= $this->render_partial('admin/specification/_field', compact('rule', 'id', 'name')) ?>
+    <? endforeach ?>
+    </fieldset>
+
+<? if (count($entries_user) > 0) : ?>
+    <fieldset>
+        <legend>
+            <?= _('Personenbezogene Informationen') ?>
+        </legend>
+    <? foreach ($entries_user as $id => $entry) : ?>
+        <?= $this->render_partial('admin/specification/_field', [
+            'rule' => $rule,
+            'id'   => $entry->datafield_id,
+            'name' => $entry->name,
+        ]) ?>
+    <? endforeach ?>
+    </fieldset>
+<? endif ?>
 
-    <? if (count($entries_user) > 0) : ?>
-        <fieldset>
-            <legend>
-                <?= _('Personenbezogene Informationen') ?>
-            </legend>
-            <? foreach ($entries_user as $id => $entry) : ?>
-                <?= $this->render_partial('admin/specification/_field',
-                        array_merge(compact('rule'), ['id' => $entry->datafield_id, 'name' => $entry->name])) ?>
-            <? endforeach ?>
-        </fieldset>
-    <? endif ?>
     <footer>
-        <? if ($rule) : ?>
-            <?= Button::createAccept(_('Übernehmen'), 'uebernehmen', ['title' => _('Änderungen übernehmen')]) ?>
-        <? else : ?>
-            <?= Button::createAccept(_('Erstellen'), 'erstellen', ['title' => _('Neue Regel erstellen')]) ?>
-        <? endif ?>
-        <?= LinkButton::createCancel(_('Abbrechen'), $controller->url_for('admin/specification'), ['title' => _('Zurück zur Übersicht')]) ?>
+    <? if ($rule->isNew()) : ?>
+        <?= Button::createAccept(_('Erstellen'), 'erstellen', ['title' => _('Neue Regel erstellen')]) ?>
+    <? else : ?>
+        <?= Button::createAccept(_('Übernehmen'), 'uebernehmen', ['title' => _('Änderungen übernehmen')]) ?>
+    <? endif ?>
+        <?= LinkButton::createCancel(_('Abbrechen'), $controller->indexURL(), ['title' => _('Zurück zur Übersicht')]) ?>
     </footer>
 </form>
-
-<?
-$sidebar = Sidebar::Get();
-if ($GLOBALS['perm']->have_perm('root')) {
-    $actions = new ActionsWidget();
-    $actions->addLink(_('Datenfelder bearbeiten'), URLHelper::getLink('dispatch.php/admin/datafields'), Icon::create('add', 'clickable'));
-    $sidebar->addWidget($actions);
-}
-?>
diff --git a/app/views/admin/specification/index.php b/app/views/admin/specification/index.php
index b2b42ea5886..4538b6a0bca 100644
--- a/app/views/admin/specification/index.php
+++ b/app/views/admin/specification/index.php
@@ -1,62 +1,63 @@
 <?php
 /**
  * @var Admin_SpecificationController $controller
- * @var AuxLockRules[] $allrules
+ * @var AuxLockRule[] $rules
  */
 ?>
 <form method="post" class="default">
     <?= CSRFProtection::tokenTag() ?>
-    <table class="default">
+    <table class="default <? if (count($rules) > 0) echo 'sortable-table'; ?>" data-sortlist="[[0, 0]]">
         <caption>
             <?= _('Verwaltung von Zusatzangaben') ?>
         </caption>
         <colgroup>
-            <col style="width: 45%">
-            <col style="width: 45%">
-            <col style="width: 10%">
+            <col style="width: 40%">
+            <col>
+            <col style="width: 10ex">
+            <col style="width: 8ex">
         </colgroup>
         <thead>
             <tr>
-                <th><?= _('Name') ?></th>
-                <th><?= _('Beschreibung') ?></th>
-                <th><?= _('Aktionen') ?></th>
+                <th data-sort="text"><?= _('Name') ?></th>
+                <th data-sort="text"><?= _('Beschreibung') ?></th>
+                <th data-sort="htmldata">
+                    <abbr title="<?= _('Anzahl der zugeordneten Veranstaltungen') ?>">#</abbr>
+                </th>
+                <th class="actions" data-sort="false"><?= _('Aktionen') ?></th>
             </tr>
         </thead>
         <tbody>
-        <? if (!empty($allrules)): ?>
-            <? foreach ($allrules as $index => $rule) : ?>
-                <tr>
-                    <td>
-                        <?= htmlReady($rule['name']) ?>
-                    </td>
-                    <td>
-                        <?= htmlReady($rule['description']) ?>
-                    </td>
-                    <td class="actions">
-                        <a href="<?=$controller->url_for('admin/specification/edit/'.$rule['lock_id']) ?>" style="vertical-align: bottom">
-                            <?= Icon::create('edit', 'clickable', ['title' => _('Regel bearbeiten')])->asImg() ?>
-                        </a>
-                        <?=Icon::create('trash', 'clickable', tooltip2(_('Regel löschen')))->asInput([
-                            'formaction'   => $controller->url_for('admin/specification/delete/' . $rule['lock_id']),
-                            'data-confirm' => sprintf(_('Wollen Sie die Regel "%s" wirklich löschen?'), $rule['name']),
-                            'style'        => 'vertical-align: bottom'
-                        ])?>
-                    </td>
-                </tr>
-            <? endforeach ?>
-        <? else : ?>
+        <? if (count($rules) === 0): ?>
             <tr>
-                <td colspan="3" style="text-align: center">
+                <td colspan="4" style="text-align: center">
                     <?= _('Es wurden noch keine Zusatzangaben definiert.') ?>
                 </td>
             </tr>
         <? endif ?>
+        <? foreach ($rules as $index => $rule) : ?>
+            <tr>
+                <td><?= htmlReady($rule->name) ?></td>
+                <td><?= htmlReady(Studip\Markup::removeHtml($rule->description)) ?></td>
+                <td data-sort-value="<?= count($rule->courses) ?>">
+                    <?= number_format(count($rule->courses), 0, ',', '.') ?>
+                </td>
+                <td class="actions">
+                    <a href="<?= $controller->edit($rule) ?>">
+                        <?= Icon::create('edit')->asImg(['title' => _('Regel bearbeiten')]) ?>
+                    </a>
+                <? if (count($rule->courses) > 0): ?>
+                    <?= Icon::create('trash', Icon::ROLE_INACTIVE)->asImg(
+                        tooltip2(_('Die Regel kann nicht gelöscht werden, da sie noch verwendet wird.'))
+                    ) ?>
+                <? else: ?>
+                    <?= Icon::create('trash')->asInput(tooltip2(_('Regel löschen')) + [
+                        'formaction'   => $controller->deleteURL($rule),
+                        'data-confirm' => sprintf(_('Wollen Sie die Regel "%s" wirklich löschen?'), $rule->name),
+                    ]) ?>
+                <? endif; ?>
+                </td>
+            </tr>
+        <? endforeach ?>
         </tbody>
     </table>
 </form>
-<?
-
-$sidebar = Sidebar::Get();
-$actions = new ActionsWidget();
-$actions->addLink(_('Neue Regel anlegen'), $controller->url_for('admin/specification/edit'), Icon::create('add', 'clickable'));
-$sidebar->addWidget($actions);
diff --git a/app/views/course/members/additional_input.php b/app/views/course/members/additional_input.php
index 89df041a6b2..50fd3b09156 100644
--- a/app/views/course/members/additional_input.php
+++ b/app/views/course/members/additional_input.php
@@ -1,30 +1,38 @@
+<?php
+/**
+ * @var DataFieldEntryModel[] $datafields
+ * @var AuxLockRule $aux
+ * @var bool $editable
+ */
+$editable = false;
+?>
 <form class="default" method="post">
-    <? if ($datafields) : ?>
-        <fieldset>
-            <legend>
-                <?= htmlReady($aux->name) ?>
-            </legend>
+<? if ($datafields) : ?>
+    <fieldset>
+        <legend>
+            <?= htmlReady($aux->name) ?>
+        </legend>
 
-            <p><?= formatReady($aux->description) ?></p>
+        <p><?= formatReady($aux->description) ?></p>
 
-            <? foreach ($datafields as $field): ?>
-                <? if ($field->getTypedDatafield()->isVisible()): ?>
-                    <? if ($field->getTypedDatafield()->isEditable()) : ?>
-                        <? $editable = true; ?>
-                    <? endif ?>
-                    <?= $field->getTypedDatafield()->getHTML('aux'); ?>
-                <? endif; ?>
-            <? endforeach; ?>
-        </fieldset>
+        <? foreach ($datafields as $field): ?>
+            <? if ($field->getTypedDatafield()->isVisible()): ?>
+                <? if ($field->getTypedDatafield()->isEditable()) : ?>
+                    <? $editable = true; ?>
+                <? endif ?>
+                <?= $field->getTypedDatafield()->getHTML('aux'); ?>
+            <? endif; ?>
+        <? endforeach; ?>
+    </fieldset>
 
-        <? if ($editable): ?>
-        <footer>
-            <?= \Studip\Button::create(_('Speichern'), 'save') ?>
-        </footer>
-        <? else: ?>
-            <?= MessageBox::info(_('Keine einstellbaren Zusatzdaten vorhanden')) ?>
-        <? endif; ?>
-    <? else : ?>
+    <? if ($editable): ?>
+    <footer>
+        <?= Studip\Button::create(_('Speichern'), 'save') ?>
+    </footer>
+    <? else: ?>
         <?= MessageBox::info(_('Keine einstellbaren Zusatzdaten vorhanden')) ?>
     <? endif; ?>
+<? else : ?>
+    <?= MessageBox::info(_('Keine einstellbaren Zusatzdaten vorhanden')) ?>
+<? endif; ?>
 </form>
diff --git a/db/migrations/5.3.9_convert_aux_lock_rules_json_fields.php b/db/migrations/5.3.9_convert_aux_lock_rules_json_fields.php
new file mode 100644
index 00000000000..56b1c2b571a
--- /dev/null
+++ b/db/migrations/5.3.9_convert_aux_lock_rules_json_fields.php
@@ -0,0 +1,56 @@
+<?php
+final class ConvertAuxLockRulesJsonFields extends Migration
+{
+    public function description()
+    {
+        return parent::description(); // TODO: Change the autogenerated stub
+    }
+
+    protected function up()
+    {
+        $query = "SELECT `lock_id`, `attributes`, `sorting` FROM `aux_lock_rules`";
+        $rows = DBManager::get()->fetchAll($query);
+
+        $query = "UPDATE `aux_lock_rules`
+                  SET `attributes` = :attributes,
+                      `sorting` = :sorting
+                  WHERE `lock_id` = :id";
+        $statement = DBManager::get()->prepare($query);
+
+        foreach ($rows as $row) {
+            $attributes = json_decode($row['attributes'], true) ?: [];
+            $attributes = array_filter($attributes);
+            $attributes = array_keys($attributes);
+
+            $sorting = json_decode($row['sorting'], true) ?: [];
+            $sorting = array_filter($sorting, function ($id) use ($attributes) {
+                return in_array($id, $attributes);
+            }, ARRAY_FILTER_USE_KEY);
+
+            $statement->bindValue(':id', $row['lock_id']);
+            $statement->bindValue(':attributes', json_encode($attributes));
+            $statement->bindValue(':sorting', json_encode($sorting));
+            $statement->execute();
+        }
+    }
+
+    protected function down()
+    {
+        $query = "SELECT `lock_id`, `attributes` FROM `aux_lock_rules`";
+        $rows = DBManager::get()->fetchAll($query);
+
+        $query = "UPDATE `aux_lock_rules`
+                  SET `attributes` = :attributes
+                  WHERE `lock_id` = :id";
+        $statement = DBManager::get()->prepare($query);
+
+        foreach ($rows as $row) {
+            $attributes = json_decode($row['attributes'], true) ?: [];
+            $attributes = array_fill_keys($attributes, '1');
+
+            $statement->bindValue(':id', $row['lock_id']);
+            $statement->bindValue(':attributes', json_encode($attributes));
+            $statement->execute();
+        }
+    }
+}
diff --git a/lib/classes/AuxLockRules.class.php b/lib/classes/AuxLockRules.class.php
index 6a6dfd9e0dd..4d06ca2d9d2 100644
--- a/lib/classes/AuxLockRules.class.php
+++ b/lib/classes/AuxLockRules.class.php
@@ -23,6 +23,9 @@
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
 
+/**
+ * @deprecated since Stud.IP 5.3
+ */
 class AuxLockRules
 {
 
diff --git a/lib/classes/Markup.class.php b/lib/classes/Markup.class.php
index af298beaffa..0f5a5c87232 100644
--- a/lib/classes/Markup.class.php
+++ b/lib/classes/Markup.class.php
@@ -233,7 +233,7 @@ class Markup
      * Create HTML purifier instance with Stud.IP-specific configuration.
      *
      * @param  boolean $autoformat  Apply the AutoFormat rules
-     * @return HTMLPurifier A new instance of the HTML purifier.
+     * @return \HTMLPurifier A new instance of the HTML purifier.
      */
     private static function createPurifier($autoformat)
     {
diff --git a/lib/classes/StudipArrayObject.class.php b/lib/classes/StudipArrayObject.class.php
index bb5444c6188..f025ffbb8b8 100644
--- a/lib/classes/StudipArrayObject.class.php
+++ b/lib/classes/StudipArrayObject.class.php
@@ -436,4 +436,12 @@ class StudipArrayObject implements IteratorAggregate, ArrayAccess, Serializable,
             throw new InvalidArgumentException("{$key} is a protected property, use a different key");
         }
     }
+
+    /**
+     * Returns whether the given value is in the underlying array.
+     */
+    public function contains($value): bool
+    {
+        return in_array($value, $this->storage);
+    }
 }
diff --git a/lib/classes/WidgetContainer.php b/lib/classes/WidgetContainer.php
index b4aaa8cdd05..108477a3844 100644
--- a/lib/classes/WidgetContainer.php
+++ b/lib/classes/WidgetContainer.php
@@ -46,10 +46,11 @@ abstract class WidgetContainer
     /**
      * Add a widget to the container.
      *
-     * @param Widget $widget The actual widget
+     * @template W of Widget
+     * @param W $widget The actual widget
      * @param String $index  Optional index/name of the widget, defaults to
      *                       class name without "widget"
-     * @return Widget The added widget to allow for easier handling
+     * @return W The added widget to allow for easier handling
      */
     public function addWidget(Widget $widget, $index = null)
     {
diff --git a/lib/models/AuxLockRule.php b/lib/models/AuxLockRule.php
index d0686a9ab99..4bf8e8bcf25 100644
--- a/lib/models/AuxLockRule.php
+++ b/lib/models/AuxLockRule.php
@@ -13,31 +13,75 @@
  * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
  * @category    Stud.IP
  * @since       3.0
+ *
  * @property string lock_id database column
  * @property string id alias column for lock_id
  * @property string name database column
  * @property string description database column
- * @property string attributes database column
- * @property string sorting database column
+ * @property JSONArrayObject attributes database column
+ * @property JSONArrayObject sorting database column
  * @property array datafields computed column
  * @property string order computed column
- * @property Course course belongs_to Course
+ * @property Course[]|SimpleORMapCollection courses has_and_belongs_to_many Courses
  */
 class AuxLockRule extends SimpleORMap
 {
     protected static function configure($config = [])
     {
         $config['db_table'] = 'aux_lock_rules';
-        $config['belongs_to']['course'] = [
-            'class_name' => Course::class,
-            'foreign_key' => 'lock_id',
-            'assoc_foreign_key' => 'aux_lock_rule',
+
+        $config['has_many'] = [
+            'courses' => [
+                'class_name'        => Course::class,
+                'foreign_key'       => 'lock_id',
+                'assoc_foreign_key' => 'aux_lock_rule',
+            ],
+        ];
+
+        $config['additional_fields'] = [
+            'datafields' => true
+        ];
+
+        $config['serialized_fields'] = [
+            'attributes' => JSONArrayObject::class,
+            'sorting'    => JSONArrayObject::class,
         ];
-        $config['additional_fields']['datafields'] = true;
-        $config['additional_fields']['order'] = true;
+
+        $config['i18n_fields'] = [
+            'name'        => true,
+            'description' => true,
+        ];
+
+        $config['registered_callbacks'] = [
+            'before_store' => [
+                function (AuxLockRule $rule) {
+                    $rule->sorting = array_filter($rule->sorting->getArrayCopy(), function ($id) use ($rule) {
+                        return $rule->attributes->contains($id);
+                    }, ARRAY_FILTER_USE_KEY);
+                },
+            ],
+            'before_delete' => [
+                function (AuxLockRule $rule) {
+                    return count($rule->courses) === 0;
+                },
+            ]
+        ];
+
         parent::configure($config);
     }
 
+    public static function findOneByCourse(Course $course): ?AuxLockRule
+    {
+        return self::findOneByCourseId($course->id);
+    }
+
+    public static function findOneByCourseId(string $course_id): ?AuxLockRule
+    {
+        $condition = "JOIN seminare ON lock_id = aux_lock_rule
+                      WHERE Seminar_id = ?";
+        return self::findOneBySQL($condition, [$course_id]);
+    }
+
     /**
      * Cache to avoid loading datafields for a user more than once
      */
@@ -50,8 +94,8 @@ class AuxLockRule extends SimpleORMap
      */
     public function getDatafields()
     {
-        $attributes = json_decode($this->attributes, true) ?: [];
-        $sorting    = json_decode($this->sorting, true) ?: [];
+        $attributes = $this->attributes->getArrayCopy();
+        $sorting    = $this->sorting->getArrayCopy();
 
         foreach ($attributes as $key => $attr) {
             if (!$attr) {
@@ -64,11 +108,8 @@ class AuxLockRule extends SimpleORMap
 
     /**
      * Updates a datafield of a courseMember by the given data
-     *
-     * @param object $member
-     * @param object $data
      */
-    public function updateMember($member, $data)
+    public function updateMember(CourseMember $member, array $data)
     {
         foreach ($data as $key => $value) {
             $datafield = current($this->getDatafield($member, $key));
@@ -88,23 +129,22 @@ class AuxLockRule extends SimpleORMap
      */
     public function getCourseData($course = null, $display_only = false)
     {
-
         // set course
         if (!$course) {
             $course = $this->course;
         }
 
         $mapping = [
-            'vadozent' => join(', ', $course->members->findBy('status', 'dozent')->getUserFullname()),
+            'vadozent'   => join(', ', $course->members->findBy('status', 'dozent')->getUserFullname()),
             'vasemester' => $course->start_semester->name,
-            'vatitle' => $course->name,
-            'vanr' => $course->VeranstaltungsNummer,
+            'vatitle'    => $course->name,
+            'vanr'       => $course->veranstaltungsnummer,
         ];
         $head_mapping = [
-            'vadozent' => _('Dozenten'),
+            'vadozent'   => _('Dozenten'),
             'vasemester' => _('Semester'),
-            'vatitle' => _('Veranstaltungstitel'),
-            'vanr' => _('Veranstaltungsnummer'),
+            'vatitle'    => _('Veranstaltungstitel'),
+            'vanr'       => _('Veranstaltungsnummer'),
         ];
 
         // start collecting entries
@@ -145,38 +185,44 @@ class AuxLockRule extends SimpleORMap
         return $result;
     }
 
-    public function getMemberData($member)
+    public function getMemberData(CourseMember $member)
     {
         $datafields = SimpleCollection::createFromArray(DatafieldEntryModel::findByModel($member));
 
         $result = [];
-        foreach (array_keys($this->datafields) as $field) {
+        foreach ($this->attributes as $field) {
             // since we have no only datafields we have to filter!
-            if ($new = $datafields->findOneBy('datafield_id', $field)) {
+            $new = $datafields->findOneBy('datafield_id', $field);
+            if ($new) {
                 $result[] = $new;
             }
         }
+
+        usort($result, function (DatafieldEntryModel $a, DatafieldEntryModel $b) {
+            $a_order = $this->sorting[$a->datafield_id] ?? 0;
+            $b_order = $this->sorting[$b->datafield_id] ?? 0;
+            return $a_order - $b_order;
+        });
+
         return $result;
     }
 
     /**
      * Caching for the datafields
-     * @param type $member
-     * @param type $fieldID
-     * @return null
      */
-     private function getDatafield($member, $fieldID)
+     private function getDatafield(CourseMember $member, $field_id): ?array
      {
-         if (mb_strlen($fieldID) == 32) {
-             if (!array_key_exists($fieldID, $this->datafieldCache)) {
-                 $this->datafieldCache[$fieldID] = DataField::find($fieldID);
+         if (mb_strlen($field_id) === 32) {
+             if (!array_key_exists($field_id, $this->datafieldCache)) {
+                 $this->datafieldCache[$field_id] = DataField::find($field_id);
              }
-             if (isset($this->datafieldCache[$fieldID])) {
-                 if ($this->datafieldCache[$fieldID]->object_type == 'usersemdata') {
-                     $field = current(DatafieldEntryModel::findByModel($member, $fieldID));
+             if (isset($this->datafieldCache[$field_id])) {
+                 $field = null;
+                 if ($this->datafieldCache[$field_id]->object_type === 'usersemdata') {
+                     $field = current(DatafieldEntryModel::findByModel($member, $field_id));
                  }
-                 if ($this->datafieldCache[$fieldID]->object_type == 'user') {
-                     $field = current(DatafieldEntryModel::findByModel(User::find($member->user_id), $fieldID));
+                 if ($this->datafieldCache[$field_id]->object_type === 'user') {
+                     $field = current(DatafieldEntryModel::findByModel(User::find($member->user_id), $field_id));
                  }
                  if ($field) {
                      $range_id = $field->sec_range_id ? [$field->range_id, $field->sec_range_id] : $field->range_id;
@@ -185,6 +231,19 @@ class AuxLockRule extends SimpleORMap
                  }
              }
          }
+
+         return null;
      }
 
+     public static function validateFields(array $fields): bool
+     {
+         $entries = DataField::getDataFields('usersemdata');
+         foreach ($entries as $entry) {
+             if (in_array($entry->id, $fields)) {
+                 return true;
+             }
+         }
+
+         return false;
+     }
 }
diff --git a/lib/models/DatafieldEntryModel.class.php b/lib/models/DatafieldEntryModel.class.php
index fa7a878cad1..882b37ed22c 100644
--- a/lib/models/DatafieldEntryModel.class.php
+++ b/lib/models/DatafieldEntryModel.class.php
@@ -48,37 +48,44 @@ class DatafieldEntryModel extends SimpleORMap implements PrivacyObject
      */
     public static function findByModel(SimpleORMap $model, $datafield_id = null)
     {
-        $mask = ["user" => 1, "autor" => 2, "tutor" => 4, "dozent" => 8, "admin" => 16, "root" => 32];
+        $mask = [
+            'user'   => 1,
+            'autor'  => 2,
+            'tutor'  => 4,
+            'dozent' => 8,
+            'admin'  => 16,
+            'root'   => 32,
+        ];
 
         $sec_range_id = null;
-        if (is_a($model, "Course")) {
+        if ($model instanceof Course) {
             $params[':institution_ids'] = $model->institutes->pluck('institut_id');
             $object_class = SeminarCategories::GetByTypeId($model->status)->id;
             $object_type = 'sem';
-            $range_id = $model->getId();
-        } elseif(is_a($model, "Institute")) {
-            $params[':institution_ids'] = [$model->Institut_id];
+            $range_id = $model->id;
+        } elseif ($model instanceof Institute) {
+            $params[':institution_ids'] = [$model->id];
             $object_class = $model->type;
             $object_type = 'inst';
-            $range_id = $model->getId();
-        } elseif(is_a($model, "User")) {
+            $range_id = $model->id;
+        } elseif ($model instanceof User) {
             $params[':institution_ids'] = $model->institute_memberships->pluck('institut_id');
             $object_class = $mask[$model->perms];
             $object_type = 'user';
-            $range_id = $model->getId();
-        } elseif(is_a($model, "CourseMember")) {
+            $range_id = $model->id;
+        } elseif($model instanceof CourseMember) {
             $params[':institution_ids'] = $model->course->institutes->pluck('institut_id');
             $object_class = $mask[$model->status];
             $object_type = 'usersemdata';
             $range_id = $model->user_id;
             $sec_range_id = $model->seminar_id;
-        } elseif(is_a($model, "InstituteMember")) {
+        } elseif($model instanceof InstituteMember) {
             $params[':institution_ids'] = [$model->institut_id];
             $object_class = $mask[$model->inst_perms];
             $object_type = 'userinstrole';
             $range_id = $model->user_id;
             $sec_range_id = $model->institut_id;
-        } elseif (is_a($model, 'ModulDeskriptor')) {
+        } elseif ($model instanceof ModulDeskriptor) {
             $params[':institution_ids'] = '';
             if (!empty($model->modul->responsible_institute->institut_id)) {
                 $params[':institution_ids'] = [$model->modul->responsible_institute->institut_id];
@@ -86,7 +93,7 @@ class DatafieldEntryModel extends SimpleORMap implements PrivacyObject
             $object_class = $model->getVariant();
             $object_type = 'moduldeskriptor';
             $range_id = $model->deskriptor_id;
-        } elseif (is_a($model, 'ModulteilDeskriptor')) {
+        } elseif ($model instanceof ModulteilDeskriptor) {
             $params[':institution_ids'] = [$model->modulteil->modul->responsible_institute->institut_id];
             $object_class = $model->getVariant();
             $object_type = 'modulteildeskriptor';
@@ -106,46 +113,50 @@ class DatafieldEntryModel extends SimpleORMap implements PrivacyObject
             $object_class = $model->getVariant();
             $object_type = 'studycourse';
             $range_id = $model->studiengang_id;
-        }
-
-        if (!$object_type) {
+        } else {
             throw new InvalidArgumentException('Wrong type of model: ' . get_class($model));
         }
-        $one_datafield = '';
+
+        $query = "SELECT a.*, b.*, a.datafield_id, b.datafield_id AS isset_content
+                  FROM datafields a
+                  LEFT JOIN datafields_entries b
+                    ON (a.datafield_id=b.datafield_id AND range_id = :range_id AND sec_range_id = :sec_range_id)
+                  WHERE object_type = :object_type
+                    AND (lang IS NULL OR lang = '')
+                    AND (a.institut_id IS NULL OR a.institut_id IN (:institution_ids))";
+
         if ($datafield_id !== null) {
-            $one_datafield = ' AND a.datafield_id = ' . DBManager::get()->quote($datafield_id);
-        } else {
-            $one_datafield = '';
+            $query .= ' AND a.datafield_id = :one_datafield_id';
+            $params[':one_datafield_id'] = $datafield_id;
         }
 
-        $query = "SELECT a.*, b.*,a.datafield_id,b.datafield_id as isset_content ";
-        $query .= "FROM datafields a LEFT JOIN datafields_entries b ON (a.datafield_id=b.datafield_id AND range_id = :range_id AND sec_range_id = :sec_range_id) ";
-        $query .= "WHERE object_type = :object_type AND (ISNULL(lang) OR lang = '') AND (a.institut_id IS NULL OR a.institut_id IN (:institution_ids))";
-
         if ($object_type === 'studycourse') {
-            $query .= "AND (LOCATE(:object_class, object_class) OR LOCATE('all', object_class)) $one_datafield ORDER BY priority";
+            $query .= " AND (LOCATE(:object_class, object_class) OR LOCATE('all', object_class)) ORDER BY priority";
             $params = array_merge($params,[
-                ':range_id' => (string) $range_id,
+                ':range_id'     => (string) $range_id,
                 ':sec_range_id' => (string) $sec_range_id,
-                ':object_type' => $object_type,
-                ':object_class' => (string) $object_class]);
+                ':object_type'  => $object_type,
+                ':object_class' => (string) $object_class,
+            ]);
         } elseif ($object_type === 'moduldeskriptor'
                 || $object_type === 'modulteildeskriptor') {
             // find datafields by language (string)
-            $query .= "AND (LOCATE(:object_class, object_class) OR object_class IS NULL) $one_datafield ORDER BY priority";
+            $query .= " AND (LOCATE(:object_class, object_class) OR object_class IS NULL) ORDER BY priority";
             $params = array_merge($params,[
-                ':range_id' => (string) $range_id,
+                ':range_id'     => (string) $range_id,
                 ':sec_range_id' => (string) $sec_range_id,
-                ':object_type' => $object_type,
-                ':object_class' => (string) $object_class]);
+                ':object_type'  => $object_type,
+                ':object_class' => (string) $object_class,
+            ]);
         } else {
             // find datafields by perms or status (int)
-            $query .= "AND ((object_class & :object_class) OR object_class IS NULL) $one_datafield ORDER BY priority";
+            $query .= " AND ((object_class & :object_class) OR object_class IS NULL) ORDER BY priority";
             $params = array_merge($params, [
-                ':range_id' => (string) $range_id,
+                ':range_id'     => (string) $range_id,
                 ':sec_range_id' => (string) $sec_range_id,
-                ':object_type' => $object_type,
-                ':object_class' => (int) $object_class]);
+                ':object_type'  => $object_type,
+                ':object_class' => (int) $object_class,
+            ]);
         }
 
         $st = DBManager::get()->prepare($query);
@@ -181,6 +192,7 @@ class DatafieldEntryModel extends SimpleORMap implements PrivacyObject
         }
         return $ret;
     }
+
     public function setContentLanguage($language)
     {
         if (!Config::get()->CONTENT_LANGUAGES[$language]) {
diff --git a/templates/i18n/input.php b/templates/i18n/input.php
index 9c9a456ad49..3ab92a5b6c7 100644
--- a/templates/i18n/input.php
+++ b/templates/i18n/input.php
@@ -1,3 +1,13 @@
+<?php
+/**
+ * @var array $languages
+ * @var array $attributes
+ * @var string $base_lang
+ * @var string $name
+ * @var I18NString $value
+ *
+ */
+?>
 <? foreach ($languages as $locale => $lang): ?>
     <?
         $attr = $attributes;
-- 
GitLab