diff --git a/app/controllers/consultation/admin.php b/app/controllers/consultation/admin.php index 12c77a7edb98d2dbb52d060a3b42479266ff55c7..0ca50b37c0297cfacac59d0f416b362e54a0790e 100644 --- a/app/controllers/consultation/admin.php +++ b/app/controllers/consultation/admin.php @@ -140,11 +140,11 @@ class Consultation_AdminController extends ConsultationController $block = new ConsultationBlock(); $block->range = $this->range; - $this->responsible = $block->responsible_persons; + $this->responsible = $block->getPossibleResponsibilites(); } elseif ($this->range instanceof Institute) { $block = new ConsultationBlock(); $block->range = $this->range; - $this->responsible = $block->responsible_persons; + $this->responsible = $block->getPossibleResponsibilites(); } } @@ -182,10 +182,20 @@ class Consultation_AdminController extends ConsultationController $block->confirmation_text = trim(Request::get('confirmation-text')) ?: null; $block->note = Request::get('note'); $block->size = Request::int('size', 1); - $block->teacher_id = Request::option('teacher_id') ?: null; $block->createSlots(Request::int('duration')); $stored += $block->store(); + + // Store block responsibilites + foreach (Request::getArray('responsibilities') as $type => $ids) { + foreach ($ids as $id) { + ConsultationResponsibility::create([ + 'block_id' => $block->id, + 'range_id' => $id, + 'range_type' => $type, + ]); + } + } } } catch (OverlapException $e) { $this->keepRequest(); @@ -211,13 +221,9 @@ class Consultation_AdminController extends ConsultationController $this->relocate('consultation/admin'); } - public function note_action($block_id, $slot_id = null, $page = 0) + public function note_action($block_id, $slot_id, $page = 0) { - if ($slot_id) { - PageLayout::setTitle(_('Anmerkung zu diesem Termin bearbeiten')); - } else { - PageLayout::setTitle(_('Anmerkung zu diesem Block bearbeiten')); - } + PageLayout::setTitle(_('Anmerkung zu diesem Termin bearbeiten')); $this->block = $this->loadBlock($block_id); $this->slot_id = $slot_id; @@ -228,20 +234,10 @@ class Consultation_AdminController extends ConsultationController $note = trim(Request::get('note')); - $changed = false; - if ($slot_id) { - $slot = $this->block->slots->find($slot_id); - $slot->note = $note; - $changed = $slot->store(); - } else { - $this->block->note = $note; - foreach ($this->block->slots as $slot) { - $slot->note = ''; - } - $changed = $this->block->store(); - } - if ($changed) { - PageLayout::postSuccess(_('Der Block wurde bearbeitet')); + $slot = $this->block->slots->find($slot_id); + $slot->note = $note; + if ($slot->store()) { + PageLayout::postSuccess(_('Die Anmerkung wurde bearbeitet')); } if ($this->block->is_expired) { @@ -344,20 +340,60 @@ class Consultation_AdminController extends ConsultationController } } - public function edit_room_action($block_id, $page = 0) + public function edit_action($block_id, $page = 0) { - PageLayout::setTitle(_('Ort des Blocks bearbeiten')); + PageLayout::setTitle(_('Block bearbeiten')); $this->block = $this->loadBlock($block_id); $this->page = $page; + + $this->responsible = false; + if ($this->block->range instanceof Course || $this->block->range instanceof Institute) { + $this->responsible = $this->block->getPossibleResponsibilites(); + } } - public function store_room_action($block_id, $page = 0) + public function store_edited_action($block_id, $page = 0) { CSRFProtection::verifyUnsafeRequest(); $this->block = $this->loadBlock($block_id); - $this->block->room = Request::get('room'); + $this->block->room = trim(Request::get('room')); + $this->block->note = trim(Request::get('note')); + + foreach ($this->block->slots as $slot) { + $slot->note = ''; + } + + // Store block responsibilites + $responsibilities = array_merge( + ['user' => [], 'statusgroup' => [], 'institute' => []], + Request::getArray('responsibilities') + ); + foreach ($responsibilities as $type => $ids) { + $of_type = $this->block->responsibilities->filter(function ($responsibility) use ($type) { + return $responsibility->range_type === $type; + }); + + // Delete removed responsibilites + $of_type->each(function ($responsibility) use ($ids) { + if (!in_array($responsibility->range_id, $ids)) { + $responsibility->delete(); + } + }); + // Add new responsibilities + foreach ($ids as $id) { + if (!$of_type->findOneBy('range_id', $id)) { + ConsultationResponsibility::create([ + 'block_id' => $this->block->id, + 'range_id' => $id, + 'range_type' => $type, + ]); + } + } + } + + $this->block->store(); PageLayout::postSuccess(_('Der Block wurde gespeichert.')); @@ -577,7 +613,6 @@ class Consultation_AdminController extends ConsultationController function ($slot) use (&$deleted) { $index = $slot->is_expired ? 'expired' : 'current'; - $slot->removeEvent(); $deleted[$index] += $slot->delete(); }, "JOIN consultation_blocks USING (block_id) WHERE range_id = ? AND range_type = ?", diff --git a/app/controllers/consultation/consultation_controller.php b/app/controllers/consultation/consultation_controller.php index c69ab16584b1057389f91cdbeddd72822c6d77b7..eba81ff4967ce57ce8f97dd9857e95c1d77e1ab1 100644 --- a/app/controllers/consultation/consultation_controller.php +++ b/app/controllers/consultation/consultation_controller.php @@ -77,6 +77,12 @@ abstract class ConsultationController extends AuthenticatedController $this->flash['request'] = Request::getInstance()->getIterator()->getArrayCopy(); } + /** + * @param $block_id + * + * @return ConsultationBlock|ConsultationBlock[] + * @throws AccessDeniedException + */ protected function loadBlock($block_id) { if (is_array($block_id)) { diff --git a/app/views/consultation/admin/block-responsibilities.php b/app/views/consultation/admin/block-responsibilities.php new file mode 100644 index 0000000000000000000000000000000000000000..ea3531a0295e37d991a97d9e73c3de2af41d6bc9 --- /dev/null +++ b/app/views/consultation/admin/block-responsibilities.php @@ -0,0 +1,50 @@ +<?php +$block = $block ?? false; +$selected = function ($type, $id) use ($block) { + if (!$block ) { + return ''; + } + $matched = $block->responsibilities->filter(function ($responsibility) use ($type, $id) { + return $responsibility->range_type === $type && $responsibility->range_id === $id; + }); + return count($matched) > 0 ? 'selected' : ''; +} +?> +<? if (!empty($responsible['users'])): ?> + <label> + <?= _('Durchführende Person(en)') ?> + <select name="responsibilities[user][]" multiple class="nested-select"> + <? foreach ($responsible['users'] as $user): ?> + <option value="<?= htmlReady($user->id) ?>" <?= $selected('user', $user->id) ?>> + <?= htmlReady($user->getFullName()) ?> + </option> + <? endforeach; ?> + </select> + </label> +<? endif; ?> + +<? if (!empty($responsible['groups'])): ?> + <label> + <?= _('Durchführende Gruppe(n)') ?> + <select name="responsibilities[statusgroup][]" multiple class="nested-select"> + <? foreach ($responsible['groups'] as $group): ?> + <option value="<?= htmlReady($group->id) ?>" <?= $selected('statusgroup', $group->id) ?>> + <?= htmlReady($group->getName()) ?> + </option> + <? endforeach; ?> + </select> + </label> +<? endif; ?> + +<? if (!empty($responsible['institutes'])): ?> + <label> + <?= _('Durchführende Einrichtung(en)') ?> + <select name="responsibilities[institute][]" multiple class="nested-select"> + <? foreach ($responsible['institutes'] as $institute): ?> + <option value="<?= htmlReady($institute->id) ?>" <?= $selected('institute', $institute->id) ?>> + <?= htmlReady($institute->getFullname()) ?> + </option> + <? endforeach; ?> + </select> + </label> +<? endif; ?> diff --git a/app/views/consultation/admin/create.php b/app/views/consultation/admin/create.php index 4630ceeeb5444d01c6e8c85f3bdbb7d8d8792a36..d8727ef2e797379607632c67322f888bf679c2d4 100644 --- a/app/views/consultation/admin/create.php +++ b/app/views/consultation/admin/create.php @@ -36,7 +36,7 @@ $intervals = [ <fieldset> <legend> - <?= _('Neue Terminblöcke anlegen') ?> + <?= _('Ort und Zeit') ?> </legend> <label> @@ -119,19 +119,18 @@ $intervals = [ <input required type="text" name="size" id="size" min="1" max="50" value="<?= Request::int('size', 1) ?>"> </label> + </fieldset> - <? if ($responsible): ?> - <label> - <?= _('Durchführende Person') ?> - <select name="teacher_id"> - <option value=""></option> - <? foreach ($responsible as $user): ?> - <option value="<?= htmlReady($user->id) ?>"> - <?= htmlReady($user->getFullName()) ?> - </option> - <? endforeach; ?> - </select> - <? endif; ?> +<? if ($responsible): ?> + <fieldset> + <legend><?= _('Durchführende Person(en), Gruppe(n) oder Einrichtung(en)') ?></legend> + + <?= $this->render_partial('consultation/admin/block-responsibilities.php', compact('responsible')) ?> + </fieldset> +<? endif; ?> + + <fieldset> + <legend><?= _('Weitere Einstellungen') ?></legend> <label> <?= _('Information zu den Terminen in diesem Block') ?> diff --git a/app/views/consultation/admin/edit.php b/app/views/consultation/admin/edit.php new file mode 100644 index 0000000000000000000000000000000000000000..6f1911a1e1a6be7be137d61e5a6fdb404e800423 --- /dev/null +++ b/app/views/consultation/admin/edit.php @@ -0,0 +1,34 @@ +<form action="<?= $controller->store_edited($block, $page) ?>" method="post" class="default"> + <?= CSRFProtection::tokenTag() ?> + + <?= MessageBox::info( + _('Das Ändern der Informationen wird auch alle Termine dieses Blocks ändern.') + )->hideClose() ?> + + <fieldset> + <legend><?= _('Terminblock bearbeiten') ?></legend> + + <label> + <span class="required"><?= _('Ort') ?></span> + <input required type="text" name="room" placeholder="<?= _('Ort') ?>" + value="<?= htmlReady($block->room) ?>"> + </label> + + <label> + <?=_('Information zu den Terminen in diesem Block') ?> (<?= _('Öffentlich einsehbar') ?>) + <textarea name="note"><?= htmlReady($block->note ) ?></textarea> + </label> + + <? if ($responsible): ?> + <?= $this->render_partial('consultation/admin/block-responsibilities.php', compact('responsible', 'block')) ?> + <? endif; ?> + </fieldset> + + <footer data-dialog-button> + <?= Studip\Button::createAccept(_('Speichern')) ?> + <?= Studip\LinkButton::createCancel( + _('Abbrechen'), + $controller->indexURL($page) + ) ?> + </footer> +</form> diff --git a/app/views/consultation/admin/edit_room.php b/app/views/consultation/admin/edit_room.php index 6c5243621c7a847a9eaee1d6089cdb5818e359fa..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 --- a/app/views/consultation/admin/edit_room.php +++ b/app/views/consultation/admin/edit_room.php @@ -1,21 +0,0 @@ -<form action="<?= $controller->store_room($block, $page) ?>" method="post" class="default"> - <?= CSRFProtection::tokenTag() ?> - - <fieldset> - <legend><?= _('Ort des Terminblocks bearbeiten') ?></legend> - - <label> - <span class="required"><?= _('Ort') ?></span> - <input required type="text" name="room" placeholder="<?= _('Ort') ?>" - value="<?= htmlReady($block->room) ?>"> - </label> - </fieldset> - - <footer data-dialog-button> - <?= Studip\Button::createAccept(_('Speichern')) ?> - <?= Studip\LinkButton::createCancel( - _('Abbrechen'), - $controller->indexURL($page) - ) ?> - </footer> -</form> diff --git a/app/views/consultation/admin/index.php b/app/views/consultation/admin/index.php index 5d0a7dac0d14da6a9e830bd8ed2785df4fd8843f..6ee75508c5e6920d00b274342961e4727def9052 100644 --- a/app/views/consultation/admin/index.php +++ b/app/views/consultation/admin/index.php @@ -45,13 +45,8 @@ </th> <th class="actions"> <?= ActionMenu::get()->addLink( - $controller->edit_roomURL($block['block'], $page), - _('Raum bearbeiten'), - Icon::create('edit'), - ['data-dialog' => 'size=auto'] - )->addLink( - $controller->noteURL($block['block'], 0, $page), - _('Information bearbeiten'), + $controller->editURL($block['block'], 0, $page), + _('Bearbeiten'), Icon::create('edit'), ['data-dialog' => 'size=auto'] )->addLink( diff --git a/app/views/consultation/block-description.php b/app/views/consultation/block-description.php index df3d0bd5c77d6d0fa8b5f756a8a0ad9080a58ffb..4d037ff5c4fa862226d3156670eadc3e7518479a 100644 --- a/app/views/consultation/block-description.php +++ b/app/views/consultation/block-description.php @@ -6,13 +6,6 @@ date('H:i', $block->end) ) ?> -<? if ($block->teacher): ?> -/ -<a href="<?= URLHelper::getLink('dispatch.php/profile', ['username' => $block->teacher->username]) ?>"> - <?= htmlReady($block->teacher->getFullName()) ?> -</a> -<? endif; ?> - (<?= formatLinks($block->room) ?>) <? if ($block->show_participants): ?> @@ -20,6 +13,19 @@ <?= tooltipIcon(_('Die Namen der buchenden Person sind sichtbar')) ?> <? endif; ?> +<? if (count($block->responsibilities) > 0): ?> +<br> +<ul class="narrow list-csv"> +<? foreach ($block->responsibilities as $responsibility): ?> + <li> + <a href="<?= URLHelper::getLink($responsibility->getURL(), [], true) ?>"> + <?= htmlReady($responsibility->getName()) ?> + </a> + </li> +<? endforeach; ?> +</ul> +<? endif; ?> + <? if ($block->note): ?> <br> <small> diff --git a/db/migrations/1.269_fix_missing_consultation_events.php b/db/migrations/1.269_fix_missing_consultation_events.php index 1432458abf10aabb11cd97f0693f6579be48b90a..a0548e0dd1aa3a6a41476c1e3d4692b34b948ca4 100644 --- a/db/migrations/1.269_fix_missing_consultation_events.php +++ b/db/migrations/1.269_fix_missing_consultation_events.php @@ -26,7 +26,7 @@ class FixMissingConsultationEvents extends Migration // has code changes for Stud.IP 5.0 this will fail but we can neglect // that since the event is already updated. try { - $slot->updateEvent(); + $slot->updateEvents(); } catch (Exception $e) { } }, @@ -41,10 +41,11 @@ class LegacyConsultationSlot extends ConsultationSlot * Updates the teacher event that belongs to the slot. This will either be * set to be unoccupied, occupied by only one user or by a group of user. */ - public function updateEvent() + public function updateEvents() { if (count($this->bookings) === 0 && !$this->block->calendar_events) { - return $this->removeEvent(); + $this->events->delete(); + return; } $teacher = User::find($this->block->teacher_id); diff --git a/db/migrations/5.1.7_consultation_multiple_responsible_ranges.php b/db/migrations/5.1.7_consultation_multiple_responsible_ranges.php new file mode 100644 index 0000000000000000000000000000000000000000..1daf474eb7d0165445a57e66dafd93e80073fc83 --- /dev/null +++ b/db/migrations/5.1.7_consultation_multiple_responsible_ranges.php @@ -0,0 +1,99 @@ +<?php +/** + * @see https://gitlab.studip.de/studip/studip/-/issues/132 + */ +final class ConsultationMultipleResponsibleRanges extends Migration +{ + public function description() + { + return 'Adjust database to allow multiple responsible ranges for consultations'; + } + + protected function up() + { + $query = "CREATE TABLE IF NOT EXISTS `consultation_responsibilities` ( + `block_id` INT(11) UNSIGNED NOT NULL, + `range_id` CHAR(32) CHARSET latin1 COLLATE latin1_bin NOT NULL, + `range_type` ENUM('user', 'institute', 'statusgroup') CHARSET latin1 COLLATE latin1_bin NOT NULL, + `mkdate` INT(11) UNSIGNED NOT NULL, + PRIMARY KEY (`block_id`, `range_id`, `range_type`) + )"; + DBManager::get()->exec($query); + + $query = "CREATE TABLE IF NOT EXISTS `consultation_events` ( + `slot_id` INT(11) UNSIGNED NOT NULL, + `user_id` CHAR(32) CHARSET latin1 COLLATE latin1_bin NOT NULL, + `mkdate` INT(11) UNSIGNED NOT NULL, + PRIMARY KEY (`slot_id`, `user_id`) + )"; + DBManager::get()->exec($query); + + $query = "INSERT IGNORE INTO `consultation_responsibilities` ( + `block_id`, `range_id`, `range_type`, `mkdate` + ) + SELECT `block_id`, `teacher_id`, 'user', UNIX_TIMESTAMP() + FROM `consultation_blocks` + WHERE `teacher_id` IS NOT NULL"; + DBManager::get()->exec($query); + + $query = "INSERT IGNORE INTO `consultation_events` ( + `slot_id`, `user_id`, `event_id`, `mkdate` + ) + SELECT `slot_id`, `teacher_id`, `teacher_event_id`, UNIX_TIMESTAMP() + FROM `consultation_blocks` + JOIN `consultation_slots` USING (`block_id`) + WHERE `teacher_event_id` IS NOT NULL"; + DBManager::get()->exec($query); + + $query = "ALTER TABLE `consultation_blocks` + DROP COLUMN `teacher_id`"; + DBManager::get()->exec($query); + + $query = "ALTER TABLE `consultation_slots` + DROP COLUMN `teacher_event_id`"; + DBManager::get()->exec($query); + } + + protected function down() + { + $query = "ALTER TABLE `consultation_slots` + ADD COLUMN `teacher_event_id` CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL AFTER `note`"; + DBManager::get()->exec($query); + + $query = "ALTER TABLE `consultation_blocks` + ADD COLUMN `teacher_id` CHAR(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL"; + DBManager::get()->exec($query); + + $query = "UPDATE `consultation_slots` AS cs + JOIN `consultation_events` AS ce USING (`slot_id`) + JOIN `consultation_blocks` AS cb USING (`block_id`) + SET cs.`teacher_event_id` = ce.`event_id` + WHERE cb.`range_type` = 'user' + AND cs.`slot_id` IN ( + SELECT `slot_id` + FROM `consultation_events` + GROUP BY `slot_id` + HAVING COUNT(*) = 1 + )"; + DBManager::get()->exec($query); + + $query = "UPDATE `consultation_blocks` AS cb + JOIN `consultation_responsibilities` AS cr USING (`block_id`) + SET cb.`teacher_id` = cr.`range_id` + WHERE cb.`block_id` IN ( + SELECT `block_id` + FROM `consultation_responsibilities` AS cr2 + JOIN `consultation_blocks` AS cb USING (`block_id`) + WHERE cr2.`range_type` = 'user' + GROUP BY `block_id` + HAVING COUNT(DISTINCT cr.`range_id`) = 1 + )"; + DBManager::get()->exec($query); + + $query = "DROP TABLE IF EXISTS `consultation_events`"; + DBManager::get()->exec($query); + + $query = "DROP TABLE IF EXISTS `consultation_responsibilities`"; + DBManager::get()->exec($query); + } +} diff --git a/lib/classes/ConsultationMailer.php b/lib/classes/ConsultationMailer.php index 007bb472d109f27f5f2d58fc8ed85f221a35423c..b9713dc55f9fe8a15da3ed7da805bf5aa23eed8b 100644 --- a/lib/classes/ConsultationMailer.php +++ b/lib/classes/ConsultationMailer.php @@ -54,9 +54,13 @@ class ConsultationMailer * * @param ConsultationBooking $booking The booking */ - public static function sendBookingMessageToTeacher(ConsultationBooking $booking) + public static function sendBookingMessageToResponsibilities(ConsultationBooking $booking) { foreach ($booking->slot->block->responsible_persons as $user) { + if ($user->id === $GLOBALS['user']->id) { + continue; + } + self::sendMessage( $user, $booking, @@ -94,7 +98,7 @@ class ConsultationMailer self::sendMessage( $receiver, $booking->slot, - sprintf(_('Grund des Termins bei bearbeitet'), $booking->slot->block->range_display), + sprintf(_('Grund des Termins bei %s bearbeitet'), $booking->slot->block->range_display), $booking->reason ); } @@ -105,9 +109,13 @@ class ConsultationMailer * @param ConsultationBooking $booking The booking * @param String $reason Reason of the cancelation */ - public static function sendCancelMessageToTeacher(ConsultationBooking $booking, $reason = '') + public static function sendCancelMessageToResponsibilities(ConsultationBooking $booking, $reason = '') { foreach ($booking->slot->block->responsible_persons as $user) { + if ($user->id === $GLOBALS['user']->id) { + continue; + } + self::sendMessage( $user, $booking, diff --git a/lib/models/CalendarEvent.class.php b/lib/models/CalendarEvent.class.php index f0a4a8711a654efa5b868191abd1fca5ea3b5c9a..a61922c2c7db82b753686df9e58d44a922de82c1 100644 --- a/lib/models/CalendarEvent.class.php +++ b/lib/models/CalendarEvent.class.php @@ -69,10 +69,10 @@ class CalendarEvent extends SimpleORMap implements Event, PrivacyObject 'foreign_key' => 'event_id', 'assoc_foreign_key' => 'student_event_id', ]; - $config['belongs_to']['consultation_slot'] = [ - 'class_name' => ConsultationSlot::class, + $config['has_many']['consultation_events'] = [ + 'class_name' => ConsultationEvent::class, 'foreign_key' => 'event_id', - 'assoc_foreign_key' => 'teacher_event_id', + 'assoc_foreign_key' => 'event_id', ]; $config['additional_fields']['type'] = true; $config['additional_fields']['name'] = true; @@ -88,10 +88,7 @@ class CalendarEvent extends SimpleORMap implements Event, PrivacyObject $event->consultation_booking->student_event_id = null; $event->consultation_booking->store(); } - if ($event->consultation_slot) { - $event->consultation_slot->teacher_event_id = null; - $event->consultation_slot->store(); - } + $event->consultation_events->delete(); }; parent::configure($config); @@ -1018,21 +1015,18 @@ class CalendarEvent extends SimpleORMap implements Event, PrivacyObject } /** - * - * TODO remove! not used? - * - * @return type + * @return string */ public function getName() { switch ($this->type) { case 'user': - return $this->user->getFullname(); + return (string) $this->user->getFullname(); case 'sem': - return $this->course->name; + return (string) $this->course->name; case 'inst': case 'fak': - return $this->institute->name; + return (string) $this->institute->name; } } diff --git a/lib/models/ConsultationBlock.php b/lib/models/ConsultationBlock.php index bb83515165723e53d5188af6fabf6c46a6e4cb26..d64462988e1b4b91f97252cf3b8d3da76664d364 100644 --- a/lib/models/ConsultationBlock.php +++ b/lib/models/ConsultationBlock.php @@ -24,7 +24,8 @@ * @property bool has_bookings computed column * @property Range range computed column * @property SimpleORMapCollection slots has_many ConsultationSlot - * @property User teacher belongs_to User + * @property ConsultationResponsibility[] responsibilities has_many ConsultationResponsibility + * @property User[] responsible_persons */ class ConsultationBlock extends SimpleORMap implements PrivacyObject { @@ -36,16 +37,18 @@ class ConsultationBlock extends SimpleORMap implements PrivacyObject { $config['db_table'] = 'consultation_blocks'; - $config['belongs_to']['teacher'] = [ - 'class_name' => User::class, - 'foreign_key' => 'teacher_id', - ]; $config['has_many']['slots'] = [ 'class_name' => ConsultationSlot::class, 'assoc_foreign_key' => 'block_id', 'on_store' => 'store', 'on_delete' => 'delete', ]; + $config['has_many']['responsibilities'] = [ + 'class_name' => ConsultationResponsibility::class, + 'assoc_foreign_key' => 'block_id', + 'on_delete' => 'delete', + 'order_by' => "ORDER BY range_type = 'user' DESC, range_type = 'statusgroup' DESC", + ]; $config['additional_fields']['range'] = [ 'set' => function ($block, $field, Range $range) { @@ -61,31 +64,11 @@ class ConsultationBlock extends SimpleORMap implements PrivacyObject return $block->range->getFullName() . ' <' . $block->range->email . '>'; } if ($block->range instanceof Course || $block->range instanceof Institute) { - $display = $block->range->getFullName(); - if ($block->teacher) { - $display .= ' (' . $block->teacher->getFullName() . ')'; - } - return $display; - } - - throw new Exception('Not implemented yet'); - }; - $config['additional_fields']['responsible_persons']['get'] = function ($block) { - if ($block->range instanceof User) { - return [$block->range]; - } - if ($block->range instanceof Course && $block->teacher) { - return [$block->teacher]; - } - - if ($block->range instanceof Course) { - return $block->range->getMembersWithStatus('tutor dozent', true)->pluck('user'); + return sprintf(_('Veranstaltung: %s'), $block->range->getFullName()); } if ($block->range instanceof Institute) { - return $block->range->members->filter(function ($member) { - return in_array($member->inst_perms, ['tutor', 'dozent']); - })->pluck('user'); + return sprintf(_('Einrichtung: %s'), $block->range->getFullname()); } throw new Exception('Not implemented yet'); @@ -103,6 +86,32 @@ class ConsultationBlock extends SimpleORMap implements PrivacyObject }); }; + $config['additional_fields']['responsible_persons']['get'] = function (ConsultationBlock $block) { + if (count($block->responsibilities) !== 0) { + $result = []; + foreach (array_merge(...$block->responsibilities->getUsers()) as $user) { + $result[$user->id] = $user; + } + return array_values($result); + } + + if ($block->range instanceof User) { + return [$block->range]; + } + if ($block->range instanceof Course) { + return ConsultationResponsibility::getCourseResponsibilities($block->range); + } + if ($block->range instanceof Institute) { + return ConsultationResponsibility::getInstituteResponsibilites($block->range); + } + + throw new Exception('Unknown range type'); + }; + + $config['registered_callbacks']['after_store'][] = function (ConsultationBlock $block) { + $block->slots->updateEvents(); + }; + parent::configure($config); } @@ -273,6 +282,37 @@ class ConsultationBlock extends SimpleORMap implements PrivacyObject return $this->range->isAccessibleToUser(); } + /** + * + */ + public function getPossibleResponsibilites() + { + if ($this->range instanceof User) { + return [ + 'users' => [$this->range] + ]; + } + + if ($this->range instanceof Course) { + return [ + 'users' => $this->range->getMembersWithStatus('tutor dozent', true)->pluck('user'), + ]; + } + + if ($this->range instanceof Institute) { + $users = $this->range->members->filter(function ($member) { + return in_array($member->inst_perms, ['tutor', 'dozent']); + })->pluck('user'); + + $groups = $this->range->status_groups; + $institutes = $this->range->sub_institutes; + + return compact('users', 'groups', 'institutes'); + } + + throw new Exception('Not implemented yet'); + } + /** * Export available data of a given user into a storage object * (an instance of the StoredUserData class) for that user. diff --git a/lib/models/ConsultationBooking.php b/lib/models/ConsultationBooking.php index 37756e1282fc81e6f608e270427321aa76883ee4..1f269c43765df19becc7129912db796f2e30c739 100644 --- a/lib/models/ConsultationBooking.php +++ b/lib/models/ConsultationBooking.php @@ -40,7 +40,8 @@ class ConsultationBooking extends SimpleORMap implements PrivacyObject 'on_delete' => 'delete', ]; - $config['registered_callbacks']['before_create'][] = function ($booking) { + // Create student event + $config['registered_callbacks']['before_create'][] = function (ConsultationBooking $booking) { setTempLanguage($booking->user_id); $event = $booking->slot->createEvent($booking->user); @@ -57,41 +58,37 @@ class ConsultationBooking extends SimpleORMap implements PrivacyObject $booking->student_event_id = $event->id; }; - $config['registered_callbacks']['after_create'][] = function ($booking) { + $config['registered_callbacks']['after_create'][] = function (ConsultationBooking $booking) { ConsultationMailer::sendBookingMessageToUser($booking); - - $responsible_persons = $booking->slot->block->responsible_persons; - if (!in_array($GLOBALS['user']->id, $responsible_persons)) { - ConsultationMailer::sendBookingMessageToTeacher($booking); - } + ConsultationMailer::sendBookingMessageToResponsibilities($booking); }; - $config['registered_callbacks']['before_store'][] = function ($booking) { + $config['registered_callbacks']['before_store'][] = function (ConsultationBooking $booking) { if (!$booking->isNew() && $booking->isFieldDirty('reason')) { if ($GLOBALS['user']->id !== $booking->user_id) { ConsultationMailer::sendReasonMessage($booking,$booking->user); } $responsible_persons = $booking->slot->block->responsible_persons; - if (!in_array($GLOBALS['user']->id, $responsible_persons)) { - foreach ($responsible_persons as $user) { + foreach ($responsible_persons as $user) { + if ($GLOBALS['user']->id !== $user->id) { ConsultationMailer::sendReasonMessage($booking, $user); } } } }; - $config['registered_callbacks']['after_store'][] = function ($booking) { + $config['registered_callbacks']['after_store'][] = function (ConsultationBooking $booking) { if ($booking->event) { $booking->event->description = $booking->reason; $booking->event->store(); } - $booking->slot->updateEvent(); + $booking->slot->updateEvents(); }; - $config['registered_callbacks']['after_delete'][] = function ($booking) { - $booking->slot->updateEvent(); + $config['registered_callbacks']['after_delete'][] = function (ConsultationBooking $booking) { + $booking->slot->updateEvents(); }; parent::configure($config); @@ -103,9 +100,7 @@ class ConsultationBooking extends SimpleORMap implements PrivacyObject ConsultationMailer::sendCancelMessageToUser($this, $reason); } - if (!in_array($GLOBALS['user']->id, $this->slot->block->responsible_persons)) { - ConsultationMailer::sendCancelMessageToTeacher($this, $reason); - } + ConsultationMailer::sendCancelMessageToResponsibilities($this, $reason); return $this->delete() ? 1 : 0; } diff --git a/lib/models/ConsultationEvent.php b/lib/models/ConsultationEvent.php new file mode 100644 index 0000000000000000000000000000000000000000..d02e2d8a0b041219c25eab8fb7a224b5f90bfce7 --- /dev/null +++ b/lib/models/ConsultationEvent.php @@ -0,0 +1,34 @@ +<?php +/** + * @author Jan-Hendrik Willms <tleilax+studip@gmail.com> + * @license GPL2 or any later version + * @since Stud.IP 5.1 + * + * @property int slot_id database column + * @property int id alias column for slot_id + * @property string user_id database column + * @property string event_id database column + * @property int mkdate database column + * @property ConsultationSlot slot belongs_to ConsultationSlot + * @property EventData event belongs_to Event + */ +class ConsultationEvent extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'consultation_events'; + + $config['belongs_to']['slot'] = [ + 'class_name' => ConsultationSlot::class, + 'foreign_key' => 'slot_id', + ]; + $config['belongs_to']['event'] = [ + 'class_name' => EventData::class, + 'foreign_key' => 'event_id', + 'assoc_foreign_key' => 'event_id', + 'on_delete' => 'delete', + ]; + + parent::configure($config); + } +} diff --git a/lib/models/ConsultationResponsibility.php b/lib/models/ConsultationResponsibility.php new file mode 100644 index 0000000000000000000000000000000000000000..9634d415394c8d0ec0e7c59ce35a00164a1fcf94 --- /dev/null +++ b/lib/models/ConsultationResponsibility.php @@ -0,0 +1,126 @@ +<?php +/** + * @author Jan-Hendrik Willms <tleilax+studip@gmail.com> + * @license GPL2 or any later version + * @since Stud.IP 5.1 + * + * @property int block_id database column + * @property int id alias column for block_id + * @property string range_id database column + * @property string range_type database column + * @property int mkdate database column + */ +class ConsultationResponsibility extends SimpleORMap +{ + protected static function configure($config = []) + { + $config['db_table'] = 'consultation_responsibilities'; + + $config['belongs_to']['block'] = [ + 'class_name' => ConsultationBlock::class, + 'foreign_key' => 'block_id', + ]; + + parent::configure($config); + } + + /** + * Returns the name of the associated responsibility. + * + * @return string + * @throws Exception + */ + public function getName() + { + if ($this->range_type === 'user') { + return User::find($this->range_id)->getFullName(); + } + if ($this->range_type === 'statusgroup') { + return Statusgruppen::find($this->range_id)->getName(); + } + if ($this->range_type === 'institute') { + return Institute::find($this->range_id)->getFullName(); + } + throw new Exception('Unknown range type'); + } + + /** + * Returns an url to the associated responsibility. + * + * @return string + * @throws Exception + */ + public function getURL() + { + if ($this->range_type === 'user') { + $user = User::find($this->range_id); + return URLHelper::getURL('dispatch.php/profile', ['username' => $user->username], true); + } + // TODO: Check if staff tab is activated and link to that + if ($this->range_type === 'statusgroup') { + $institute = Statusgruppen::find($this->range_id)->institute; + return URLHelper::getURL('dispatch.php/institute/overview', ['auswahl' => $institute->id], true); + } + if ($this->range_type === 'institute') { + return URLHelper::getURL('dispatch.php/institute/overview', ['auswahl' => $this->range_id], true); + } + throw new Exception('Unknown range type'); + } + + /** + * Returns all users belonging to the associated responsibility. + * + * @return array + * @throws Exception + */ + public function getUsers() + { + if ($this->range_type === 'user') { + return [User::find($this->range_id)]; + } + if ($this->range_type === 'statusgroup') { + $group = Statusgruppen::find($this->range_id); + return self::getStatusgroupResponsibilities($group); + } + if ($this->range_type === 'institute') { + $institute = Institute::find($this->range_id); + return self::getInstituteResponsibilites($institute); + } + throw new Exception('Unknown range type'); + } + + /** + * Returns all responsible users for a course. + * + * @param Course $course + * @return array + */ + public static function getCourseResponsibilities(Course $course) + { + return $course->getMembersWithStatus('tutor dozent', true)->pluck('user'); + } + + /** + * Returns all responsible users for a status group. + * + * @param Statusgruppen $group + * @return array + */ + public static function getStatusgroupResponsibilities(Statusgruppen $group) + { + return $group->members->pluck('user'); + } + + /** + * Returns all responsible users for an institute. + * + * @param Institute $institute + * @return array + */ + public static function getInstituteResponsibilites(Institute $institute) + { + return $institute->members->filter(function (InstituteMember $member) { + return in_array($member->inst_perms, ['tutor', 'dozent']); + })->pluck('user'); + } +} diff --git a/lib/models/ConsultationSlot.php b/lib/models/ConsultationSlot.php index fd3b05a72940f77a51a4f4f3bf760ebe0ac2c5e3..1177fcec98f986028dfaae5dac96614eb713d448 100644 --- a/lib/models/ConsultationSlot.php +++ b/lib/models/ConsultationSlot.php @@ -11,10 +11,9 @@ * @property string start_time database column * @property string end_time database column * @property string note database column - * @property string teacher_event_id database column * @property SimpleORMapCollection bookings has_many ConsultationBooking * @property ConsultationBlock block belongs_to ConsultationBlock - * @property EventData event has_one EventData + * @property SimpleORMapCollection events has_many EventData */ class ConsultationSlot extends SimpleORMap { @@ -30,24 +29,20 @@ class ConsultationSlot extends SimpleORMap 'class_name' => ConsultationBlock::class, 'foreign_key' => 'block_id', ]; - $config['has_one']['event'] = [ - 'class_name' => EventData::class, - 'foreign_key' => 'teacher_event_id', - 'assoc_foreign_key' => 'event_id', - 'on_delete' => 'delete', - ]; $config['has_many']['bookings'] = [ 'class_name' => ConsultationBooking::class, 'assoc_foreign_key' => 'slot_id', 'on_store' => 'store', 'on_delete' => 'delete', ]; + $config['has_many']['events'] = [ + 'class_name' => ConsultationEvent::class, + 'assoc_foreign_key' => 'slot_id', + 'on_delete' => 'delete', + ]; - $config['registered_callbacks']['before_create'][] = function ($slot) { - if ($slot->block->calendar_events && $slot->block->range_type === 'user') { - $slot->teacher_event_id = $slot->createEvent($slot->block->range)->id; - $slot->updateEvent(); - } + $config['registered_callbacks']['before_create'][] = function (ConsultationSlot $slot) { + $slot->updateEvents(); }; $config['registered_callbacks']['after_delete'][] = function ($slot) { $block = $slot->block; @@ -196,69 +191,81 @@ class ConsultationSlot extends SimpleORMap * Updates the teacher event that belongs to the slot. This will either be * set to be unoccupied, occupied by only one user or by a group of user. */ - public function updateEvent() + public function updateEvents() { - if ($this->block->range_type !== 'user') { - return; - } - // If no range is associated, remove the event if (!$this->block->range) { - return $this->removeEvent(); + $this->events->delete(); + return; } if (count($this->bookings) === 0 && !$this->block->calendar_events) { - return $this->removeEvent(); + $this->events->delete(); + return; } - $event = $this->event; - if (!$event) { - $event = $this->createEvent($this->block->range); + // Get responsible user ids + $responsible_ids = array_map( + function (User $user) { + return $user->id; + }, + $this->block->responsible_persons + ); - $this->teacher_event_id = $event->id; - $this->store(); + // Remove events for no longer responsible users + foreach ($this->events as $event) { + if (!in_array($event->user_id, $responsible_ids)) { + $event->delete(); + } } - setTempLanguage($this->block->range_id); + // Add events for missing responsible users + $missing = array_diff($responsible_ids, $this->events->pluck('user_id')); + foreach ($missing as $user_id) { + $event = $this->createEvent(User::find($user_id)); + ConsultationEvent::create([ + 'slot_id' => $this->id, + 'user_id' => $user_id, + 'event_id' => $event->id, + ]); + } + + // Reset relation in order to account to the above changes + $this->resetRelation('events'); + + foreach ($this->events as $event) { + setTempLanguage($event->user_id); - if (count($this->bookings) > 0) { - $event->category_intern = 1; + if (count($this->bookings) > 0) { + $event->event->category_intern = 1; - if (count($this->bookings) === 1) { - $booking = $this->bookings->first(); + if (count($this->bookings) === 1) { + $booking = $this->bookings->first(); - $event->summary = sprintf( - _('Termin mit %s'), - $booking->user->getFullName() - ); - $event->description = $booking->reason; + $event->event->summary = sprintf( + _('Termin mit %s'), + $booking->user->getFullName() + ); + $event->event->description = $booking->reason; + } else { + $event->event->summary = sprintf( + _('Termin mit %u Personen'), + count($this->bookings) + ); + $event->event->description = implode("\n\n----\n\n", $this->bookings->map(function ($booking) { + return "- {$booking->user->getFullName()}:\n{$booking->reason}"; + })); + } } else { - $event->summary = sprintf( - _('Termin mit %u Personen'), - count($this->bookings) - ); - $event->description = implode("\n\n----\n\n", $this->bookings->map(function ($booking) { - return "- {$booking->user->getFullName()}:\n{$booking->reason}"; - })); + $event->event->category_intern = 9; + $event->event->summary = _('Freier Termin'); + $event->event->description = _('Dieser Termin ist noch nicht belegt.'); } - } else { - $event->category_intern = 9; - $event->summary = _('Freier Termin'); - $event->description = _('Dieser Termin ist noch nicht belegt.'); - } - restoreLanguage(); + $event->event->store(); - $event->store(); - } - - public function removeEvent() - { - if ($this->event) { - $this->event->delete(); + restoreLanguage(); - $this->teacher_event_id = null; - $this->store(); } } } diff --git a/lib/models/Course.class.php b/lib/models/Course.class.php index b958a4b1ab3d531477335dc0c7f4f7dbdeecddb5..f543c88ba9112f40da48c824d1ae3b2a86018f8b 100644 --- a/lib/models/Course.class.php +++ b/lib/models/Course.class.php @@ -449,7 +449,7 @@ class Course extends SimpleORMap implements Range, PrivacyObject, StudipItem, Fe * * @param String|Array $status the status to filter with * @param bool $as_collection return collection instead of array? - * @return Array an array of all those members. + * @return Array|SimpleCollection an array of all those members. */ public function getMembersWithStatus($status, $as_collection = false) { diff --git a/lib/models/Institute.class.php b/lib/models/Institute.class.php index 86120c1e3fac6d5d25c54d09e2223e965e572f7b..743931c8a14dca613403074fc92c5fc68af6460d 100644 --- a/lib/models/Institute.class.php +++ b/lib/models/Institute.class.php @@ -41,6 +41,87 @@ class Institute extends SimpleORMap implements Range { + protected static function configure($config = []) + { + $config['db_table'] = 'Institute'; + $config['additional_fields']['is_fak']['get'] = 'isFaculty'; + + $config['has_many']['members'] = [ + 'class_name' => InstituteMember::class, + 'assoc_func' => 'findByInstitute', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['home_courses'] = [ + 'class_name' => Course::class, + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['sub_institutes'] = [ + 'class_name' => Institute::class, + 'assoc_foreign_key' => 'fakultaets_id', + 'assoc_func' => 'findByFaculty', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['datafields'] = [ + 'class_name' => DatafieldEntryModel::class, + 'assoc_foreign_key' => + function($model,$params) { + $model->setValue('range_id', $params[0]->id); + }, + 'assoc_func' => 'findByModel', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'foreign_key' => + function($i) { + return [$i]; + } + ]; + $config['belongs_to']['faculty'] = [ + 'class_name' => Institute::class, + 'foreign_key' => 'fakultaets_id', + ]; + $config['has_and_belongs_to_many']['courses'] = [ + 'class_name' => Course::class, + 'thru_table' => 'seminar_inst', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['scm'] = [ + 'class_name' => StudipScmEntry::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['status_groups'] = [ + 'class_name' => Statusgruppen::class, + 'assoc_foreign_key' => 'range_id', + 'on_delete' => 'delete', + 'on_store' => 'store', + 'order_by' => 'ORDER BY position ASC', + ]; + $config['has_many']['blubberthreads'] = [ + 'class_name' => BlubberThread::class, + 'assoc_func' => 'findByInstitut', + 'on_delete' => 'delete', + 'on_store' => 'store', + ]; + $config['has_many']['tools'] = [ + 'class_name' => ToolActivation::class, + 'assoc_foreign_key' => 'range_id', + 'order_by' => 'ORDER BY position', + 'on_delete' => 'delete', + ]; + $config['additional_fields']['all_status_groups']['get'] = function ($institute) { + return Statusgruppen::findAllByRangeId($institute->id, true); + }; + + $config['i18n_fields']['name'] = true; + $config['registered_callbacks']['after_create'][] = 'setDefaultTools'; + + parent::configure($config); + } /** * Returns the currently active course or false if none is active. @@ -60,7 +141,7 @@ class Institute extends SimpleORMap implements Range * @param string $fakultaets_id * @return array */ - static function findByFaculty($fakultaets_id) + public static function findByFaculty($fakultaets_id) { return self::findBySQL("fakultaets_id=? AND fakultaets_id <> institut_id ORDER BY Name ASC", [$fakultaets_id]); } @@ -69,7 +150,7 @@ class Institute extends SimpleORMap implements Range * returns an array of all institutes ordered by faculties and name * @return array */ - static function getInstitutes() + public static function getInstitutes() { $db = DBManager::get(); $result = $db->query("SELECT Institute.Institut_id, Institute.Name, IF(Institute.Institut_id=Institute.fakultaets_id,1,0) AS is_fak " . @@ -82,10 +163,12 @@ class Institute extends SimpleORMap implements Range /** * returns an array of all institutes to which the given user belongs, * ordered by faculties and name. The user role for each institute is included + * * @param string $user_id if omitted, the current user is used + * * @return array */ - static function getMyInstitutes($user_id = NULL) + public static function getMyInstitutes($user_id = NULL) { global $perm, $user; if (!$user_id) { @@ -125,94 +208,9 @@ class Institute extends SimpleORMap implements Range return $result; } - /** - * - */ - protected static function configure($config = []) - { - $config['db_table'] = 'Institute'; - $config['additional_fields']['is_fak']['get'] = 'isFaculty'; - - $config['has_many']['members'] = [ - 'class_name' => 'InstituteMember', - 'assoc_func' => 'findByInstitute', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['home_courses'] = [ - 'class_name' => 'Course', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['sub_institutes'] = [ - 'class_name' => 'Institute', - 'assoc_foreign_key' => 'fakultaets_id', - 'assoc_func' => 'findByFaculty', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['datafields'] = [ - 'class_name' => 'DatafieldEntryModel', - 'assoc_foreign_key' => - function($model,$params) { - $model->setValue('range_id', $params[0]->id); - }, - 'assoc_func' => 'findByModel', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'foreign_key' => - function($i) { - return [$i]; - } - ]; - $config['belongs_to']['faculty'] = [ - 'class_name' => 'Institute', - 'foreign_key' => 'fakultaets_id', - ]; - $config['has_and_belongs_to_many']['courses'] = [ - 'class_name' => 'Course', - 'thru_table' => 'seminar_inst', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['scm'] = [ - 'class_name' => 'StudipScmEntry', - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['status_groups'] = [ - 'class_name' => 'Statusgruppen', - 'assoc_foreign_key' => 'range_id', - 'on_delete' => 'delete', - 'on_store' => 'store', - 'order_by' => 'ORDER BY position ASC', - ]; - $config['has_many']['blubberthreads'] = [ - 'class_name' => 'BlubberThread', - 'assoc_func' => 'findByInstitut', - 'on_delete' => 'delete', - 'on_store' => 'store', - ]; - $config['has_many']['tools'] = [ - 'class_name' => 'ToolActivation', - 'assoc_foreign_key' => 'range_id', - 'order_by' => 'ORDER BY position', - 'on_delete' => 'delete' - ]; - $config['additional_fields']['all_status_groups']['get'] = function ($institute) { - return Statusgruppen::findAllByRangeId($institute->id, true); - }; - - $config['i18n_fields']['name'] = true; - $config['registered_callbacks']['after_create'][] = 'setDefaultTools'; - - parent::configure($config); - } - - function isFaculty() + public function isFaculty() { - return $this->fakultaets_id == $this->institut_id; + return $this->fakultaets_id === $this->institut_id; } /** @@ -221,7 +219,8 @@ class Institute extends SimpleORMap implements Range * @param string formatting template name * @return string Fullname */ - public function getFullname($format = 'default') { + public function getFullname($format = 'default'): string + { $template['type-name'] = '%2$s: %1$s'; if ($format === 'default' || !isset($template[$format])) { $format = 'type-name'; @@ -240,7 +239,7 @@ class Institute extends SimpleORMap implements Range * * @return string */ - public function describeRange() + public function describeRange(): string { return _('Einrichtung'); } @@ -250,7 +249,7 @@ class Institute extends SimpleORMap implements Range * * @return string */ - public function getRangeType() + public function getRangeType(): string { return 'institute'; } @@ -280,7 +279,7 @@ class Institute extends SimpleORMap implements Range * @return bool * @todo Check permissions */ - public function isAccessibleToUser($user_id = null) + public function isAccessibleToUser($user_id = null): bool { return true; } @@ -292,7 +291,7 @@ class Institute extends SimpleORMap implements Range * @return bool * @todo Check permissions */ - public function isEditableByUser($user_id = null) + public function isEditableByUser($user_id = null): bool { if ($user_id === null) { $user_id = $GLOBALS['user']->id; @@ -332,7 +331,7 @@ class Institute extends SimpleORMap implements Range * @param $name string name of tool / plugin * @return bool */ - public function isToolActive($name) + public function isToolActive($name): bool { $plugin = PluginEngine::getPlugin($name); return $plugin && $this->tools->findOneby('plugin_id', $plugin->getPluginId()); diff --git a/lib/models/StatusgruppeUser.php b/lib/models/StatusgruppeUser.php index cf0b85bc78872419e39fabe924501c092ff36eaf..04b42b6833d3d49f838b3b7c3cf57883806ae803 100644 --- a/lib/models/StatusgruppeUser.php +++ b/lib/models/StatusgruppeUser.php @@ -30,11 +30,11 @@ class StatusgruppeUser extends SimpleORMap implements PrivacyObject { $config['db_table'] = 'statusgruppe_user'; $config['belongs_to']['group'] = [ - 'class_name' => 'Statusgruppen', + 'class_name' => Statusgruppen::class, 'foreign_key' => 'statusgruppe_id', ]; $config['belongs_to']['user'] = [ - 'class_name' => 'User', + 'class_name' => User::class, 'foreign_key' => 'user_id', ]; diff --git a/lib/models/Statusgruppen.php b/lib/models/Statusgruppen.php index 1c0927e7064073b8a92199109ccaf0196737205d..16cd3d786965c41e3252e79cdd65d28e3853a073 100644 --- a/lib/models/Statusgruppen.php +++ b/lib/models/Statusgruppen.php @@ -33,6 +33,9 @@ * @property string children computed column * @property SimpleORMapCollection members has_many StatusgruppeUser * @property Statusgruppen parent belongs_to Statusgruppen + * @property Course course belongs_to course + * @property Institute institute belongs_to institute + * @property User user belongs_to user */ class Statusgruppen extends SimpleORMap implements PrivacyObject { @@ -42,36 +45,36 @@ class Statusgruppen extends SimpleORMap implements PrivacyObject { $config['db_table'] = 'statusgruppen'; $config['has_many']['members'] = [ - 'class_name' => 'StatusgruppeUser', + 'class_name' => StatusgruppeUser::class, 'assoc_foreign_key' => 'statusgruppe_id', 'on_delete' => 'delete', 'order_by' => 'ORDER BY position ASC', ]; $config['has_and_belongs_to_many']['dates'] = [ - 'class_name' => 'CourseDate', + 'class_name' => CourseDate::class, 'thru_table' => 'termin_related_groups', 'order_by' => 'ORDER BY date', 'on_delete' => 'delete', // TODO: This might cause trouble 'on_store' => 'store' ]; $config['belongs_to']['parent'] = [ - 'class_name' => 'Statusgruppen', + 'class_name' => Statusgruppen::class, 'foreign_key' => 'range_id', ]; $config['belongs_to']['course'] = [ - 'class_name' => 'Course', + 'class_name' => Course::class, 'foreign_key' => 'range_id', ]; $config['belongs_to']['institute'] = [ - 'class_name' => 'Institute', + 'class_name' => Institute::class, 'foreign_key' => 'range_id', ]; $config['belongs_to']['user'] = [ - 'class_name' => 'User', + 'class_name' => User::class, 'foreign_key' => 'range_id', ]; $config['has_one']['blubberthread'] = [ - 'class_name' => 'BlubberStatusgruppeThread', + 'class_name' => BlubberStatusgruppeThread::class, 'on_store' => 'store', 'on_delete' => 'delete' ]; diff --git a/lib/visual.inc.php b/lib/visual.inc.php index ae1f9d89bfca4d8dbdfbe9a176a7dbc713d57ecb..303987d76fd30811294286246546779420a462f6 100644 --- a/lib/visual.inc.php +++ b/lib/visual.inc.php @@ -635,10 +635,10 @@ function tooltip2($text, $with_alt = TRUE, $with_popup = FALSE) { * @param bool $important render icon in "important" style * @param bool $html tooltip text is HTML content */ -function tooltipIcon($text, $important = false, $html = false) +function tooltipIcon($text, $important = false, $html = false): string { if (!trim($text)) { - return; + return ''; } // render tooltip diff --git a/resources/assets/stylesheets/less/lists.less b/resources/assets/stylesheets/less/lists.less index 4e10367050b891a350f7447ac0de7bb53d45e550..20ed99425bed65325e0794e884e4bc1a09f09604 100644 --- a/resources/assets/stylesheets/less/lists.less +++ b/resources/assets/stylesheets/less/lists.less @@ -48,6 +48,12 @@ ol { content: ""; } } + + &.narrow { + > li { + display: inline-flex; + } + } } .list-pipe-separated { .list-inline();