From 9c4acc181641fe862f7b98276c00dc0f681835d2 Mon Sep 17 00:00:00 2001 From: Jan-Hendrik Willms <tleilax+studip@gmail.com> Date: Tue, 25 Jun 2024 12:46:33 +0000 Subject: [PATCH] allow copying of participants and groups when copying a course, fixes #3441 Closes #3441 Merge request studip/studip!2873 --- app/controllers/course/statusgroups.php | 158 ++++++++++---- app/controllers/course/wizard.php | 36 ++- app/views/course/statusgroups/copy.php | 36 +++ app/views/course/statusgroups/index.php | 24 +- app/views/course/wizard/summary.php | 36 ++- lib/classes/AuthenticatedController.php | 12 +- lib/classes/QuickSearch.php | 2 +- .../coursewizardsteps/BasicDataWizardStep.php | 205 ++++++++++++++---- lib/classes/searchtypes/StandardSearch.php | 2 +- lib/models/Statusgruppen.php | 74 +++++-- resources/assets/javascripts/lib/dialog.js | 4 + 11 files changed, 446 insertions(+), 143 deletions(-) create mode 100644 app/views/course/statusgroups/copy.php diff --git a/app/controllers/course/statusgroups.php b/app/controllers/course/statusgroups.php index 6f96299409f..e9b2c431aac 100644 --- a/app/controllers/course/statusgroups.php +++ b/app/controllers/course/statusgroups.php @@ -25,8 +25,6 @@ class Course_StatusgroupsController extends AuthenticatedController { parent::before_filter($action, $args); - global $perm; - checkObject(); checkObjectModule("participants"); @@ -92,7 +90,7 @@ class Course_StatusgroupsController extends AuthenticatedController $membercounts = array_column(DBManager::get()->fetchAll( "SELECT u.`statusgruppe_id`, COUNT(u.`user_id`) as membercount FROM `statusgruppen` s - JOIN `statusgruppe_user` u USING (`statusgruppe_id`) + JOIN `statusgruppe_user` u USING (`statusgruppe_id`) WHERE s.`range_id` = ? GROUP BY `statusgruppe_id` ORDER BY s.`position` ASC, s.`name` ASC", @@ -253,6 +251,7 @@ class Course_StatusgroupsController extends AuthenticatedController Icon::create('add') )->asDialog('size=auto'); } + if (Config::get()->EXPORT_ENABLE) { $export = new ExportWidget(); $export->addLink( @@ -297,6 +296,7 @@ class Course_StatusgroupsController extends AuthenticatedController Icon::create('arr_1down') ); } + $sidebar->addWidget($actions); } @@ -587,16 +587,18 @@ class Course_StatusgroupsController extends AuthenticatedController $group_id, Request::get('name'), $position, - $this->course_id, Request::int('size', 0), + $this->course_id, + Request::int('size', 0), $selfassign, - Request::int('selfassign', 0) !== 0 + $selfassign ? strtotime(Request::get('selfassign_start', 'now')) : 0, - Request::int('selfassign', 0) && Request::get('selfassign_end') + $selfassign && Request::get('selfassign_end') ? strtotime(Request::get('selfassign_end')) : 0, - Request::int('makefolder', 0), - Request::getArray('dates') + Request::bool('makefolder', false), + Request::getArray('dates'), + Request::bool('blubber', false) ); $group->description = trim(Request::get('description')) ?: null; @@ -614,22 +616,6 @@ class Course_StatusgroupsController extends AuthenticatedController )); } - $thread = BlubberStatusgruppeThread::findByStatusgruppe_id($group->id); - if (Request::get("blubber") && !$thread) { - $thread = new BlubberStatusgruppeThread(); - $thread['context_type'] = "course"; - $thread['context_id'] = $this->course_id; - $thread['user_id'] = $GLOBALS['user']->id; - $thread['external_contact'] = 0; - $thread['visible_in_stream'] = 1; - $thread['display_class'] = "BlubberStatusgruppeThread"; - $thread['commentable'] = 1; - $thread['metadata'] = ['statusgruppe_id' => $group->id]; - $thread->store(); - } elseif(!Request::get("blubber") && $thread) { - $thread->delete(); - } - $this->relocate('course/statusgroups'); } @@ -842,13 +828,17 @@ class Course_StatusgroupsController extends AuthenticatedController $numbering = Request::int('startnumber', 1); } for ($i = 0 ; $i < Request::int('number') ; $i++) { - Statusgruppen::createOrUpdate('', Request::get('prefix').' '. - $numbering++, - null, $this->course_id, Request::int('size', 0), + Statusgruppen::createOrUpdate( + null, + Request::get('prefix') . ' ' . $numbering++, + null, + $this->course_id, + Request::int('size', 0), Request::int('selfassign', 0) + Request::int('exclusive', 0), strtotime(Request::get('selfassign_start', 'now')), strtotime(Request::get('selfassign_end', 0)), - Request::int('makefolder', 0)); + Request::bool('makefolder', false) + ); $counter++; } @@ -868,12 +858,16 @@ class Course_StatusgroupsController extends AuthenticatedController })->orderBy('priority'); foreach ($topics as $t) { - $group = Statusgruppen::createOrUpdate('', _('Thema:') . ' ' . $t->title, - null, $this->course_id, Request::int('size', 0), + $group = Statusgruppen::createOrUpdate( + null, + _('Thema:') . ' ' . $t->title, + null, + $this->course_id, + Request::int('size', 0), Request::int('selfassign', 0) + Request::int('exclusive', 0), strtotime(Request::get('selfassign_start', 'now')), strtotime(Request::get('selfassign_end', 0)), - Request::int('makefolder', 0) + Request::bool('makefolder', false) ); // Connect group to dates that are assigned to the given topic. @@ -920,12 +914,17 @@ class Course_StatusgroupsController extends AuthenticatedController } } - $group = Statusgruppen::createOrUpdate('', $name, - null, $this->course_id, Request::int('size', 0), + $group = Statusgruppen::createOrUpdate( + null, + $name, + null, + $this->course_id, + Request::int('size', 0), Request::int('selfassign', 0) + Request::int('exclusive', 0), strtotime(Request::get('selfassign_start', 'now')), strtotime(Request::get('selfassign_end', 0)), - Request::int('makefolder', 0)); + Request::bool('makefolder', false) + ); // Connect group to dates that are assigned to the given cycle. foreach ($c->dates as $d) { @@ -952,12 +951,17 @@ class Course_StatusgroupsController extends AuthenticatedController $name .= ' (' . $room . ')'; } - $group = Statusgruppen::createOrUpdate('', $name, - $counter + 1, $this->course_id, Request::int('size', 0), + $group = Statusgruppen::createOrUpdate( + null, + $name, + $counter + 1, + $this->course_id, + Request::int('size', 0), Request::int('selfassign', 0) + Request::int('exclusive', 0), strtotime(Request::get('selfassign_start', 'now')), strtotime(Request::get('selfassign_end', 0)), - Request::int('makefolder', 0)); + Request::bool('makefolder', false) + ); $d->statusgruppen->append($group); $d->store(); @@ -974,12 +978,17 @@ class Course_StatusgroupsController extends AuthenticatedController CourseMember::findByCourseAndStatus($this->course_id, 'dozent'))->orderBy('position'); foreach ($lecturers as $l) { - Statusgruppen::createOrUpdate('', $l->getUserFullname('full'), - null, $this->course_id, Request::int('size', 0), + Statusgruppen::createOrUpdate( + null, + $l->getUserFullname('full'), + null, + $this->course_id, + Request::int('size', 0), Request::int('selfassign', 0) + Request::int('exclusive', 0), strtotime(Request::get('selfassign_start', 'now')), strtotime(Request::get('selfassign_end', 0)), - Request::int('makefolder', 0)); + Request::bool('makefolder', false) + ); $counter++; } @@ -1012,9 +1021,11 @@ class Course_StatusgroupsController extends AuthenticatedController // Actions for selected groups. if (Request::submitted('batch_groups')) { - if ($groups = Request::getArray('groups')) { + $groups = Request::getArray('groups'); + if ($groups) { $this->groups = SimpleCollection::createFromArray( - Statusgruppen::findMany($groups))->orderBy('position, name'); + Statusgruppen::findMany($groups) + )->orderBy('position, name'); switch (Request::option('groups_action')) { case 'edit_size': PageLayout::setTitle(_('Gruppengröße bearbeiten')); @@ -1097,6 +1108,10 @@ class Course_StatusgroupsController extends AuthenticatedController ) ); break; + case 'copy': + $this->keepRequest(); + $this->redirect($this->copyURL()); + return; case 'delete': PageLayout::setTitle(_('Gruppe(n) löschen?')); $this->askdelete = true; @@ -1232,10 +1247,17 @@ class Course_StatusgroupsController extends AuthenticatedController } foreach ($groups as $g) { - Statusgruppen::createOrUpdate($g->id, $g->name, - $g->position, $this->course_id, $g->size, - $selfassign, $selfassign_start, $selfassign_end, - false); + Statusgruppen::createOrUpdate( + $g->id, + $g->name, + $g->position, + $this->course_id, + $g->size, + $selfassign, + $selfassign_start, + $selfassign_end, + false + ); } PageLayout::postSuccess(_('Die Einstellungen der ausgewählten Gruppen wurden gespeichert.')); $this->relocate('course/statusgroups'); @@ -1503,4 +1525,48 @@ class Course_StatusgroupsController extends AuthenticatedController $this->group = $group; } + + public function copy_action(): void + { + PageLayout::setTitle(_('Gruppen in andere Veranstaltung kopieren')); + + $this->group_ids = Request::optionArray('groups'); + + $this->search = new MyCoursesSearch('Seminar_id', User::findCurrent()->perms, [ + ':userid' => User::findCurrent()->id, + ':exclude' => [$this->course_id], + ]); + + $this->response->add_header('X-Dialog-Size', 'medium'); + } + + public function do_copy_action(): void + { + if (!Request::isPost()) { + throw new MethodNotAllowedException(); + } + + $target_course_id = Request::option('course_id'); + $target_course = Course::find($target_course_id); + + BasicDataWizardStep::copyParticipantsAndGroups( + $target_course, + $this->course_id, + false, + true, + Request::bool('copy_members', false), + Request::optionArray('group_ids') + ); + + PageLayout::postSuccess(sprintf( + _('Die Gruppen wurden in die Veranstaltung %s kopiert.'), + sprintf( + '<a href="%s">%s</a>', + URLHelper::getLink('seminar_main.php', ['auswahl' => $target_course_id], true), + htmlReady($target_course->getFullName()) + ), + )); + + $this->redirect($this->indexURL()); + } } diff --git a/app/controllers/course/wizard.php b/app/controllers/course/wizard.php index c1571ad04a1..b4244b61bea 100644 --- a/app/controllers/course/wizard.php +++ b/app/controllers/course/wizard.php @@ -20,17 +20,28 @@ class Course_WizardController extends AuthenticatedController /** * @var Array steps the wizard has to execute in order to create a new course. */ - public $steps = []; + public array $steps = []; public function before_filter (&$action, &$args) { parent::before_filter($action, $args); + if ($GLOBALS['user']->perms === 'user') { + throw new AccessDeniedException(); + } + $this->dialog = Request::isXhr(); $this->studygroup = Request::bool('studygroup', $this->flash['studygroup'] ?? false); + // Feels a bit hacky + $this->is_copy = isset($_SESSION['coursewizard'][$args[1] ?? '']['source_id']); + if (!$this->studygroup) { - PageLayout::setTitle(_('Neue Veranstaltung anlegen')); + if ($this->is_copy) { + PageLayout::setTitle(_('Veranstaltung kopieren')); + } else { + PageLayout::setTitle(_('Neue Veranstaltung anlegen')); + } $navigation = new Navigation(_('Neue Veranstaltung anlegen'), 'dispatch.php/course/wizard'); Navigation::addItem('/browse/my_courses/new_course', $navigation); @@ -46,10 +57,6 @@ class Course_WizardController extends AuthenticatedController } $this->steps = CourseWizardStepRegistry::findBySQL("`enabled`=1 ORDER BY `number`"); - - if ($GLOBALS['user']->perms === 'user') { - throw new AccessDeniedException(); - } } /** @@ -132,7 +139,10 @@ class Course_WizardController extends AuthenticatedController } // The "create" button was clicked -> create course. } else if (Request::submitted('create')) { - $_SESSION['coursewizard'][$this->temp_id]['copy_basic_data'] = Request::submitted('copy_basic_data'); + $_SESSION['coursewizard'][$this->temp_id]['copy_basic_data'] = Request::bool('copy_basic_data'); + $_SESSION['coursewizard'][$this->temp_id]['copy_participants'] = Request::bool('copy_participants'); + $_SESSION['coursewizard'][$this->temp_id]['copy_groups'] = Request::bool('copy_groups'); + $_SESSION['coursewizard'][$this->temp_id]['copy_members'] = Request::bool('copy_members'); if ($this->getValues()) { // Batch creation of several courses at once. if ($batch = Request::getArray('batchcreate')) { @@ -141,7 +151,7 @@ class Course_WizardController extends AuthenticatedController $failed = 0; // Create given number of courses. for ($i = 1 ; $i <= $batch['number'] ; $i++) { - if ($newcourse = $this->createCourse($i == $batch['number'] ? true : false)) { + if ($newcourse = $this->createCourse($i == $batch['number'])) { // Add corresponding number/letter to name or number of newly created course. if ($batch['add_number_to'] == 'name') { $newcourse->name .= ' ' . $numbering; @@ -293,16 +303,20 @@ class Course_WizardController extends AuthenticatedController * Copy an existing course. */ public function copy_action($id) { - if (!$GLOBALS['perm']->have_studip_perm('dozent', $id) - || LockRules::Check($id, 'seminar_copy')) { - throw new AccessDeniedException(_("Sie dürfen diese Veranstaltung nicht kopieren")); + if ( + !$GLOBALS['perm']->have_studip_perm('dozent', $id) + || LockRules::Check($id, 'seminar_copy') + ) { + throw new AccessDeniedException(_('Sie dürfen diese Veranstaltung nicht kopieren')); } + $course = Course::find($id); $values = []; for ($i = 0 ; $i < sizeof($this->steps) ; $i++) { $step = $this->getStep($i); $values = $step->copy($course, $values); } + $values['source_id'] = $course->id; $this->initialize(); $_SESSION['coursewizard'][$this->temp_id] = $values; diff --git a/app/views/course/statusgroups/copy.php b/app/views/course/statusgroups/copy.php new file mode 100644 index 00000000000..b43f52be22a --- /dev/null +++ b/app/views/course/statusgroups/copy.php @@ -0,0 +1,36 @@ +<?php +/** + * @var Course_StatusgroupsController $controller + * @var MyCoursesSearch $search + * @var string[] $group_ids + */ +?> +<form action="<?= $controller->do_copy() ?>" method="post" class="default"> + <?= addHiddenFields('group_ids', $group_ids) ?> + + <fieldset> + <legend><?= _('Gruppen in andere Veranstaltung kopieren') ?></legend> + + <label> + <?= _('Zielveranstaltung auswählen') ?> + <?= QuickSearch::get('course_id', $search) + ->setAttributes(['required' => '']) + ->setInputStyle('width:100%') + ->withButton() + ->render(); ?> + </label> + + <label> + <input type="checkbox" name="copy_members" value="1"> + <?= _('Inklusive aller zugeordneten Personen') ?> + </label> + </fieldset> + + <footer data-dialog-button> + <?= Studip\Button::createAccept(_('Kopieren')) ?> + <?= Studip\LinkButton::createCancel( + _('Abbrechen'), + $controller->indexURL() + ) ?> + </footer> +</form> diff --git a/app/views/course/statusgroups/index.php b/app/views/course/statusgroups/index.php index d420de24f85..73a70ce98ca 100644 --- a/app/views/course/statusgroups/index.php +++ b/app/views/course/statusgroups/index.php @@ -1,5 +1,17 @@ -<form action="<?= $controller->url_for('course/statusgroups/batch_action') ?>" method="post"> -<section class="contentbox course-statusgroups" <? if ($is_tutor && !$is_locked) echo 'data-sortable="' . $controller->url_for('course/statusgroups/order') . '"'; ?>> +<?php +/** + * @var Course_StatusgroupsController $controller + * @var bool $is_tutor + * @var bool $is_locked + * @var array $groups + * @var bool $open_groups + * @var string $order + * @var string $sort_by + * @var int $ungrouped_count + */ +?> +<form action="<?= $controller->batch_action() ?>" method="post"> +<section class="contentbox course-statusgroups" <? if ($is_tutor && !$is_locked) echo 'data-sortable="' . $controller->order() . '"'; ?>> <header> <h1><?= _('Teilnehmende nach Gruppen') ?></h1> </header> @@ -29,14 +41,18 @@ </div> <div class="groupactions"> <label> - <select name="groups_action" id="batch-groups-action" disabled> + <select name="groups_action" id="batch-groups-action"> <option value="edit_size"><?= _('Gruppengröße bearbeiten') ?></option> <option value="edit_selfassign"><?= _('Selbsteintrag bearbeiten') ?></option> <option value="write_message"><?= _('Nachricht schreiben') ?></option> + <option value="copy"><?= _('In andere Veranstaltung kopieren') ?></option> <option value="delete"><?= _('Löschen') ?></option> </select> </label> - <?= Studip\Button::create(_('Ausführen'), 'batch_groups', ['data-dialog' => 'size=auto', 'disabled' => '', 'id' => 'batch-groups-submit']) ?> + <?= Studip\Button::create(_('Ausführen'), 'batch_groups', [ + 'data-dialog' => 'size=auto', + 'id' => 'batch-groups-submit', + ]) ?> </div> </footer> <?php endif ?> diff --git a/app/views/course/wizard/summary.php b/app/views/course/wizard/summary.php index ec5a646f417..6344e561c09 100644 --- a/app/views/course/wizard/summary.php +++ b/app/views/course/wizard/summary.php @@ -1,6 +1,6 @@ <?php /** - * @var Course_WikiController $controller + * @var Course_WizardController $controller * @var int $stepnumber * @var string $temp_id * @var bool $dialog @@ -24,18 +24,37 @@ ) ?> <? endif ?> -<? if ($source_course) : ?> +<? if (isset($source_course)) : ?> <section> - <label> - <input type="checkbox" checked name="copy_basic_data" value="1"> + <p> <?= sprintf( - _('Alle Grunddaten der Ursprungsveranstaltung (%s) kopieren'), + _('Folgende Daten der Ursprungsveranstaltung (%s) kopieren'), sprintf( '<a data-dialog href="%s">%s</a>', URLHelper::getLink('dispatch.php/course/details', ['sem_id' => $source_course->id]), htmlReady($source_course->getFullName()) ) ) ?> + </p> + + <label> + <input type="checkbox" checked name="copy_basic_data" value="1"> + <?= _('Grunddaten') ?> + </label> + + <label> + <input type="checkbox" name="copy_participants" value="1"> + <?= _('Reguläre Teilnehmende') ?> + </label> + + <label> + <input type="checkbox" name="copy_groups" value="1" data-activates="[name='copy_members']"> + <?= _('Statusgruppen') ?> + </label> + + <label> + <input type="checkbox" name="copy_members" value="1"> + <?= _('Zugeordnete Teilnehmende der Statusgruppen') ?> </label> </section> <? endif ?> @@ -50,9 +69,10 @@ <footer data-dialog-button> <? if (isset($_SESSION['coursewizard'][$this->temp_id]['batchcreate'])) : ?> - <? foreach ($_SESSION['coursewizard'][$this->temp_id]['batchcreate'] as $key => $value) : ?> - <input type="hidden" name="batchcreate[<?= $key ?>]" value="<?= $value ?>"> - <? endforeach ?> + <?= addHiddenFields( + 'batchcreate', + $_SESSION['coursewizard'][$this->temp_id]['batchcreate'] + ) ?> <? endif ?> <?= Studip\Button::create(_('Zurück'), 'back', $dialog ? ['data-dialog' => 'size=50%'] : []) ?> diff --git a/lib/classes/AuthenticatedController.php b/lib/classes/AuthenticatedController.php index e051ffa7156..6ed5c20bf28 100644 --- a/lib/classes/AuthenticatedController.php +++ b/lib/classes/AuthenticatedController.php @@ -1,13 +1,11 @@ <?php -/* - * Copyright (C) 2009 - Marcus Lunzenauer <mlunzena@uos.de> +/** + * @author Marcus Lunzenauer <mlunzena@uos.de> + * @copyright 2009 - Marcus Lunzenauer <mlunzena@uos.de> + * @license GPL2 or any later version * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. + * @property Trails_Flash $flash */ - class AuthenticatedController extends StudipController { protected $with_session = true; //we do need to have a session for this controller diff --git a/lib/classes/QuickSearch.php b/lib/classes/QuickSearch.php index f3e18cc27ea..b6fcc1223d6 100644 --- a/lib/classes/QuickSearch.php +++ b/lib/classes/QuickSearch.php @@ -155,7 +155,7 @@ class QuickSearch * the searchfield will automatically search for persons, courses, workgroups, institutes and * you don't need to call the specialSearch-method. * - * @return object of type QuickSearch + * @return static */ public static function get($name, $search = NULL) { diff --git a/lib/classes/coursewizardsteps/BasicDataWizardStep.php b/lib/classes/coursewizardsteps/BasicDataWizardStep.php index d47c4f3a341..c312972b4a6 100644 --- a/lib/classes/coursewizardsteps/BasicDataWizardStep.php +++ b/lib/classes/coursewizardsteps/BasicDataWizardStep.php @@ -249,7 +249,7 @@ class BasicDataWizardStep implements CourseWizardStep $values['tutors'] = []; } - list($lsearch, $tsearch) = array_values($this->getSearch($values['coursetype'], + [$lsearch, $tsearch] = array_values($this->getSearch($values['coursetype'], array_merge([$values['institute']], array_keys($values['participating'])), array_keys($values['lecturers']), array_keys($values['tutors']))); // Quicksearch for lecturers. @@ -387,26 +387,28 @@ class BasicDataWizardStep implements CourseWizardStep * Stores the given values to the given course. * * @param Course $course the course to store values for - * @param Array $values values to set - * @return Course The course object with updated values. + * @param array $values values to set + * + * @return Course|false The course object with updated values. */ public function storeValues($course, $values) { + // Fetch settings from $values before it is overwritten + $source_id = $values['source_id'] ?? null; + $copy_basic_data = !empty($values['copy_basic_data']); + $copy_participants = !empty($values['copy_participants']); + $copy_groups = !empty($values['copy_groups']); + $copy_members = !empty($values['copy_members']); + // We only need our own stored values here. - if (@$values['copy_basic_data'] === true) { - $source = Course::find($values['source_id']); - } $values = $values[__CLASS__]; $seminar = new Seminar($course); - if (isset($source)) { - $course->setData($source->toArray('untertitel ort sonstiges art teilnehmer vorrausetzungen lernorga leistungsnachweis ects admission_turnout modules')); - foreach ($source->datafields as $one) { - $df = $one->getTypedDatafield(); - if ($df->isEditable()) { - $course->datafields->findOneBy('datafield_id', $one->datafield_id)->content = $one->content; - } - } + if ($copy_basic_data) { + $this->copyBasicData( + $course, + $source_id + ); } $course->status = $values['coursetype']; @@ -440,36 +442,39 @@ class BasicDataWizardStep implements CourseWizardStep break; } } - if ($course->store()) { - StudipLog::log('SEM_CREATE', $course->id, null, 'Veranstaltung mit Assistent angelegt'); - $institutes = [$values['institute']]; - if (isset($values['participating']) && is_array($values['participating'])) { - $institutes = array_merge($institutes, array_keys($values['participating'])); - } - $seminar->setInstitutes($institutes); - if (isset($values['lecturers']) && is_array($values['lecturers'])) { - foreach (array_keys($values['lecturers']) as $user_id) { - $seminar->addMember($user_id, 'dozent'); - } - } - if (isset($values['tutors']) && is_array($values['tutors'])) { - foreach (array_keys($values['tutors']) as $user_id) { - $seminar->addMember($user_id, 'tutor'); - } + if (!$course->store()) { + return false; + } + + StudipLog::log('SEM_CREATE', $course->id, null, 'Veranstaltung mit Assistent angelegt'); + $institutes = [$values['institute']]; + if (isset($values['participating']) && is_array($values['participating'])) { + $institutes = array_merge($institutes, array_keys($values['participating'])); + } + $seminar->setInstitutes($institutes); + if (isset($values['lecturers']) && is_array($values['lecturers'])) { + foreach (array_keys($values['lecturers']) as $user_id) { + $seminar->addMember($user_id, 'dozent'); } - if (Config::get()->DEPUTIES_ENABLE && isset($values['deputies']) && is_array($values['deputies'])) { - foreach ($values['deputies'] as $d => $assigned) { - Deputy::addDeputy($d, $course->id); - } + } + if (isset($values['tutors']) && is_array($values['tutors'])) { + foreach (array_keys($values['tutors']) as $user_id) { + $seminar->addMember($user_id, 'tutor'); } - if ($semclass['admission_type_default'] == 3) { - $course_set_id = CourseSet::getGlobalLockedAdmissionSetId(); - CourseSet::addCourseToSet($course_set_id, $course->id); + } + if (Config::get()->DEPUTIES_ENABLE && isset($values['deputies']) && is_array($values['deputies'])) { + foreach ($values['deputies'] as $d => $assigned) { + Deputy::addDeputy($d, $course->id); } - return $course; - } else { - return false; } + if ($semclass['admission_type_default'] == 3) { + $course_set_id = CourseSet::getGlobalLockedAdmissionSetId(); + CourseSet::addCourseToSet($course_set_id, $course->id); + } + + self::copyParticipantsAndGroups($course, $source_id, $copy_participants, $copy_groups, $copy_members); + + return $course; } /** @@ -477,7 +482,7 @@ class BasicDataWizardStep implements CourseWizardStep * to already given values. A good example are study areas which * are only needed for certain sem_classes. * - * @param Array $values values specified from previous steps + * @param array $values values specified from previous steps * @return bool Is the current step required for a new course? */ public function isRequired($values) @@ -488,7 +493,7 @@ class BasicDataWizardStep implements CourseWizardStep /** * Copy values for basic data wizard step from given course. * @param Course $course - * @param Array $values + * @param array $values */ public function copy($course, $values) { @@ -521,17 +526,17 @@ class BasicDataWizardStep implements CourseWizardStep * Fetches the default deputies for a given person if the necessary * config options are set. * @param $user_id user whose default deputies to get - * @return Array Default deputy user_ids. + * @return array Default deputy user_ids. */ public function getDefaultDeputies($user_id) { if (Config::get()->DEPUTIES_ENABLE && Config::get()->DEPUTIES_DEFAULTENTRY_ENABLE) { - return Deputy::findDeputies($user_id)->map(function($deputy) { + return Deputy::findDeputies($user_id)->map(function (Deputy $deputy): array { return ['id' => $deputy->user_id, 'name' => $deputy->getDeputyFullname()]; }); - } else { - return []; } + + return []; } public function getSearch($course_type, $institute_ids, $exclude_lecturers = [],$exclude_tutors = []) @@ -629,4 +634,112 @@ class BasicDataWizardStep implements CourseWizardStep return $values; } + private function copyBasicData( + Course $course, + string $source_id + ): void { + $source = Course::find($source_id); + $course->setData($source->toArray('untertitel ort sonstiges art teilnehmer vorrausetzungen lernorga leistungsnachweis ects admission_turnout modules')); + foreach ($source->datafields as $one) { + $df = $one->getTypedDatafield(); + if ($df->isEditable()) { + $course->datafields->findOneBy('datafield_id', $one->datafield_id)->content = $one->content; + } + } + } + + /** + * Copies participants and/or groups from one course to another. + */ + public static function copyParticipantsAndGroups( + Course $course, + string $source_id, + bool $with_participants = true, + bool $with_groups = true, + bool $with_members = true, + bool|array $group_ids = false + ): void { + $source = Course::find($source_id); + + if (!$with_participants && !$with_groups) { + return; + } + + if ($with_participants || ($with_groups && $with_members)) { + $member_ids = false; + if (!$with_participants && $with_members) { + $member_ids = []; + $source->statusgruppen->filter(function (Statusgruppen $group) use ($group_ids): bool { + return $group_ids === false + || in_array($group->id, $group_ids); + })->each(function (Statusgruppen $group) use (&$member_ids): void { + $group->members->each(function (StatusgruppeUser $member) use (&$member_ids): void { + if (!in_array($member->user_id, $member_ids)) { + $member_ids[] = $member->user_id; + } + }); + }); + } + + $source->getMembersWithStatus(['user', 'autor', 'tutor'], true) + ->filter(function (CourseMember $member) use ($course, $member_ids): bool { + return ($member_ids === false || in_array($member->user_id, $member_ids)) + && !CourseMember::exists([$course->id, $member->user_id]); + })->each(function (CourseMember $member) use ($course): void { + CourseMember::insertCourseMember( + $course->id, + $member->user_id, + $member->status, + ); + }); + } + + if (!$with_groups) { + return; + } + + $source->statusgruppen->filter(function (Statusgruppen $group) use ($group_ids): bool { + return $group_ids === false + || in_array($group->id, $group_ids); + })->each(function (Statusgruppen $group) use ($course, $with_members, $group_ids): void { + $g = Statusgruppen::findOneBySQL( + 'range_id = ? AND name = ?', + [$course->id, $group->name] + ); + + if (!$g) { + $g = Statusgruppen::createOrUpdate( + null, + $group->name, + $group->position, + $course->id, + $group->size, + $group->selfassign, + $group->selfassign_start, + $group->selfassign_end, + $group->hasFolder(), + null, + $group->hasBlubber() + ); + } + + if (!$with_members) { + return; + } + + $group->members->filter(function (StatusgruppeUser $member) use ($g): bool { + return !StatusgruppeUser::exists([$g->id, $member->user_id]); + })->each(function (StatusgruppeUser $member) use ($g): void { + StatusgruppeUser::create([ + 'statusgruppe_id' => $g->id, + ...$member->toArray([ + 'user_id', + 'position', + 'visible', + 'inherit', + ]) + ]); + }); + }); + } } diff --git a/lib/classes/searchtypes/StandardSearch.php b/lib/classes/searchtypes/StandardSearch.php index a3f0f3b5474..6d37cbf0e7f 100644 --- a/lib/classes/searchtypes/StandardSearch.php +++ b/lib/classes/searchtypes/StandardSearch.php @@ -114,7 +114,7 @@ class StandardSearch extends SQLSearch if (empty($this->search_settings['simple_name'])) { $sql .= ", CONCAT(auth_user_md5.Nachname, ', ', auth_user_md5.Vorname, ' (', auth_user_md5.username, ')'), auth_user_md5.perms "; } else { - $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) "; + $sql .= ", CONCAT(auth_user_md5.Vorname, ' ', auth_user_md5.Nachname) "; } $sql .= "FROM auth_user_md5 LEFT JOIN user_info ON (user_info.user_id = auth_user_md5.user_id) " . "LEFT JOIN user_visibility ON (user_visibility.user_id = auth_user_md5.user_id) " . diff --git a/lib/models/Statusgruppen.php b/lib/models/Statusgruppen.php index 94c8847261d..6297f2c94ae 100644 --- a/lib/models/Statusgruppen.php +++ b/lib/models/Statusgruppen.php @@ -133,31 +133,35 @@ class Statusgruppen extends SimpleORMap implements PrivacyObject /** * Creates or updates a statusgroup. * - * @param string $id ID of an existing group or empty if new group + * @param string|null $id ID of an existing group or empty if new group * @param string $name group name - * @param int $position position or null if automatic position after other groups + * @param int|null $position position or null if automatic position after other groups * @param string $range_id ID of the object this group belongs to * @param int $size max number of members or 0 if unlimited - * @param int $selfassign may users join this group by themselves? + * @param bool $selfassign may users join this group by themselves? * @param int $selfassign_start group joining is possible starting at ... - * @param int $makefolder create a document folder assigned to this group? + * @param int $selfassign_end group joining is possible until ... + * @param bool $makefolder create a document folder assigned to this group? * @param array|null $dates dates assigned to this group. Defaults to null which means already assigned * dates are not changed. + * @param bool $make_blubber create a blubber thread for this group? + * * @return Statusgruppen The saved statusgroup. * @throws Exception */ public static function createOrUpdate( - $id, - $name, - $position, - $range_id, - $size, - $selfassign, - $selfassign_start, - $selfassign_end, - $makefolder, - $dates = null - ) + ?string $id, + string $name, + ?int $position, + string $range_id, + int $size, + bool $selfassign, + int $selfassign_start, + int $selfassign_end, + bool $makefolder, + ?array $dates = null, + bool $make_blubber = false + ): Statusgruppen { $group = new Statusgruppen($id); @@ -176,12 +180,12 @@ class Statusgruppen extends SimpleORMap implements PrivacyObject $group->store(); - /* - * Create document folder if requested (ID is needed here, - * so we do that after store()). - */ + // Create document folder if requested (ID is needed here, so we do + // that after store()). $group->updateFolder($makefolder); + $group->updateBlubber($make_blubber); + return $group; } @@ -412,6 +416,38 @@ class Statusgruppen extends SimpleORMap implements PrivacyObject } } + /** + * Returns whether the group has an associated blubber thread. + */ + public function hasBlubber(): bool + { + return (bool) BlubberStatusgruppeThread::findByStatusgruppe_id($this->id); + } + + /** + * Delete or create blubber thread + * + * @param bool $set Whether to create a blubber thread or not; an existing + * blubber thread will be deleted if $set is false + */ + public function updateBlubber(bool $set): void + { + if ($set && $this->hasBlubber()) { + BlubberStatusgruppeThread::create([ + 'context_type' => 'course', + 'context_id' => $this->range_id, + 'user_id' => User::findCurrent()->id, + 'external_contact' => false, + 'display_class' => BlubberStatusgruppeThread::class, + 'visible_in_stream' => true, + 'commentable' => true, + 'metadata' => ['statusgruppe_id' => $this->id], + ]); + } elseif (!$set) { + BlubberStatusgruppeThread::findByStatusgruppe_id($this->id)?->delete(); + } + } + /** * Finds CourseTopics assigned to this group via course dates. * @return array diff --git a/resources/assets/javascripts/lib/dialog.js b/resources/assets/javascripts/lib/dialog.js index 8f6e50adb0b..c602a29f849 100644 --- a/resources/assets/javascripts/lib/dialog.js +++ b/resources/assets/javascripts/lib/dialog.js @@ -173,6 +173,10 @@ Dialog.handlers.header['X-Title'] = function(title, options) { Dialog.handlers.header['X-No-Buttons'] = function(value, options) { options.buttons = false; }; +// Handler for HTTP header X-Dialog-Size: Adjust the size of the dialog +Dialog.handlers.header['X-Dialog-Size'] = function (value, options) { + options.size = value; +}; // Creates a dialog from an anchor, a button or a form element. // Will update the dialog if it is already open -- GitLab