diff --git a/app/controllers/admin/overlapping.php b/app/controllers/admin/overlapping.php index 8072ff59b4f402ab277f7998b7a3871c89327bac..101292b711a9f40812887afc282e921cadc2f1e7 100644 --- a/app/controllers/admin/overlapping.php +++ b/app/controllers/admin/overlapping.php @@ -17,6 +17,8 @@ class Admin_OverlappingController extends AuthenticatedController { + private ?string $view = null; + /** * Common before filter for all actions. * @@ -25,10 +27,13 @@ class Admin_OverlappingController extends AuthenticatedController */ public function before_filter(&$action, &$args) { + if (!$GLOBALS['perm']->have_perm('admin')) { + throw new AccessDeniedException(); + } parent::before_filter($action, $args); Navigation::activateItem('/browse/my_courses/overlapping'); - + URLHelper::bindLinkParam('view', $this->view); if (Request::option('sem_select')) { $GLOBALS['user']->cfg->store('MY_COURSE_SELECTED_CYCLE', Request::option('sem_select')); } @@ -41,21 +46,26 @@ class Admin_OverlappingController extends AuthenticatedController /** * Main view: Shows selection form and result. + * + * @return void */ public function index_action() { + $this->view = 'index'; $this->setSidebar(); + $selection_id = Request::option('selection', $_SESSION['MVV_OVL_SELECTION_ID'] ?? null); $selections = SimpleORMapCollection::createFromArray( MvvOverlappingSelection::findBySQL('`selection_id` = ? AND `user_id` = ?', [ - Request::option('selection'), + $selection_id, $GLOBALS['user']->id ]) ); - $this->selection_id = null; + $_SESSION['MVV_OVL_SELECTION_ID'] = $selection_id; + $this->selection_id = ''; if (count($selections)) { $this->base_version = StgteilVersion::find($selections->first()->base_version_id); - $this->fachsems = (array) explode(',', $selections->first()->fachsems); - $this->semtypes = (array) explode(',', $selections->first()->semtypes); + $this->fachsems = explode(',', $selections->first()->fachsems); + $this->semtypes = explode(',', $selections->first()->semtypes); $this->comp_versions = StgteilVersion::findMany($selections->pluck('comp_version_id')); $this->selection_id = $selections->first()->selection_id; if (Request::int('show_hidden') !== null) { @@ -67,6 +77,9 @@ class Admin_OverlappingController extends AuthenticatedController $this->fachsems = Request::intArray('fachsems'); $this->semtypes = Request::intArray('semtypes'); } + $this->base_version_id = $this->base_version->id ?? ''; + $this->comp_versions_ids = SimpleCollection::createFromArray($this->comp_versions)->pluck('id'); + $this->stgteil_versions = $this->getStgteilVersions(); $this->conflicts = MvvOverlappingSelection::getConflictsBySelection( $this->selection_id, empty($_SESSION['MVV_OVL_HIDDEN']) @@ -75,21 +88,25 @@ class Admin_OverlappingController extends AuthenticatedController /** * Resets form and shows index view. + * + * @return void */ public function reset_action() { - $this->setSidebar(); + $this->setSidebar('index'); $_SESSION['MVV_OVL_HIDDEN'] = 0; + $_SESSION['MVV_OVL_SELECTION_ID'] = ''; $this->conflicts = []; - $this->render_action('index'); + $this->redirect('admin/overlapping/index'); } /** * Calculates the conflicts and redirects to index view. + * + * @return void */ public function check_action() { - $this->setSidebar(); $this->base_version = StgteilVersion::find(Request::option('base_version')); if ($this->base_version) { $this->comp_versions = []; @@ -108,7 +125,8 @@ class Admin_OverlappingController extends AuthenticatedController $this->base_version, $this->comp_versions, $this->fachsems, - $this->semtypes + $this->semtypes, + $this->selected_semester->id ); // refresh conflicts @@ -127,7 +145,7 @@ class Admin_OverlappingController extends AuthenticatedController $selection[$comp_version->id]->base_version_id = $this->base_version->id; $selection[$comp_version->id]->comp_version_id = $comp_version->id; $selection[$comp_version->id]->setFachsemester($this->fachsems); - $selection[$comp_version->id]->setCoursetypes($this->semtypes); + $selection[$comp_version->id]->setCourseTypes($this->semtypes); $selection[$comp_version->id]->user_id = $GLOBALS['user']->id; $selection[$comp_version->id]->store(); } @@ -168,204 +186,386 @@ class Admin_OverlappingController extends AuthenticatedController /** * Shows the responsible admin of the course. * - * @param type $course_id The id of the course. + * @param string $conflict_id The id of the conflict. + * @return void */ - public function admin_info_action($course_id) + public function admin_info_action(string $conflict_id) { - $this->course = Course::find($course_id); - if ($this->course) { + $this->conflict = MvvOverlappingConflict::find($conflict_id); + $this->version = $this->conflict->comp_abschnitt->version; + $this->course = $this->conflict->comp_course; + if ($this->course && $this->version) { $this->admins = InstituteMember::findByInstituteAndStatus($this->course->institut_id, 'admin'); } else { PageLayout::postMessage(MessageBox::error(_('Unbekannte Veranstaltung.'))); } + $this->selected_view = 'admin_info'; } /** * Shows the course details. * - * @param type $course_id The id of the course. + * @param string $conflict_id The id of the conflict. + * @return void */ - public function course_info_action($course_id) + public function course_info_action(string $conflict_id) { - $course = Course::find($course_id); - if ($course) { - Request::set('sem_id', $course->id); - $this->redirect('course/details' . '?sem_id=' . $course->id); + $this->conflict = MvvOverlappingConflict::find($conflict_id); + $this->course = $this->conflict->comp_course; + $this->version = $this->conflict->comp_abschnitt->version; + if ($this->course && $this->version) { + $response = $this->relay('course/details'); + $this->content = $response->body; } else { - PageLayout::postMessage(MessageBox::error(_('Unbekannte Veranstaltung.'))); + PageLayout::postMessage(MessageBox::error(_('Unbekannte Veranstaltung oder Version.'))); } + $this->selected_view = 'course_info'; + $this->render_template('admin/overlapping/info_dialog'); } /** * Sets a course as hidden. + * + * @param int $conflict_id The id of the conflict. + * @return void */ - public function set_exclude_action() + public function exclude_action(int $conflict_id) { - $selection = MvvOverlappingSelection::find(Request::int('selection_id')); - if ($selection->user_id == $GLOBALS['user']->id) { - $exclude = new MvvOverlappingExclude([$selection->selection_id, Request::option('course_id')]); - if (Request::int('excluded')) { - $success = $exclude->delete(); - } else { + $conflict = MvvOverlappingConflict::find($conflict_id); + if ($conflict->selection->user_id === $GLOBALS['user']->id) { + $exclude = new MvvOverlappingExclude( + [ + $conflict->selection->selection_id, + $conflict->comp_course_id + ] + ); + if ($exclude->isNew()) { $success = $exclude->store(); + } else { + $success = $exclude->delete(); } $this->set_status($success ? 204 : 400); } else { $this->set_status(403); } - $this->render_nothing(); + $this->relocate('admin/overlapping/' . $this->view); } /** - * Shows detailed information about the studiengangteil version. + * Shows information of the study course version. * - * @param type $version_id The id of the studiengangteil version. + * @param string $conflict_id The id of the conflict. + * @return void + * @throws \Flexi\TemplateNotFoundException + * @throws \Trails\Exceptions\DoubleRenderError */ - public function version_info_action($version_id) + public function version_info_action(string $conflict_id) { - $version = StgteilVersion::find($version_id); - if ($version) { - Request::set('version', $version->id); - $this->redirect($this->url_for('search/studiengaenge/verlauf/' . $version->stgteil_id, - ['semester' => $this->selected_semester, - 'version' => $version->id])); - return; + $this->conflict = MvvOverlappingConflict::find($conflict_id); + if (empty($this->conflict)) { + throw new InvalidArgumentException(); + } + $this->version = $this->conflict->comp_abschnitt->version; + $this->course = $this->conflict->comp_course; + if ($this->version && $this->course) { + $response = $this->relay('search/studiengaenge/verlauf/' . $this->version->stgteil_id + . "/?semester={$this->selected_semester->id}&version={$this->version->id}"); + $this->content = $response->body; } else { - PageLayout::postMessage(MessageBox::error(_('Unbekannte Studiengangteil-Version.'))); + PageLayout::postError(_('Unbekannte Studiengangteil-Version.')); } + $this->selected_view = 'info'; + $this->render_template('admin/overlapping/info_dialog'); } /** - * Init the sidebar. + * Shows the planer view of conflicts. + * + * @param string $selection_id The id of the selection. + * @return void */ - private function setSidebar() + public function planer_action(string $selection_id = '') { - $sidebar = Sidebar::Get(); + $this->view = 'planer'; + $this->setSidebar(); - $widget = new SelectWidget( - _('Semesterauswahl'), - $this->url_for('admin/overlapping/index'), - 'sem_select' + $selection_id = $selection_id ?: $_SESSION['MVV_OVL_SELECTION_ID'] ?? null; + + $this->fullcalendar = Studip\Fullcalendar::create( + _('Kalender'), + [ + 'editable' => false, + 'selectable' => false, + 'studip_urls' => '', + 'dialog_size' => 'auto', + 'minTime' => sprintf('%02u:00', 8), + 'maxTime' => sprintf('%02u:00', 21), + 'defaultDate' => date('Y-m-d', $this->selected_semester->vorles_beginn), + 'allDaySlot' => false, + 'allDayText' => '', + 'header' => [ + 'left' => false, + 'center' => $this->selected_semester->name, + 'right' => false, + ], + 'weekNumbers' => false, + 'views' => [ + 'timeGridWeek' => [ + 'columnHeaderFormat' => ['weekday' => 'short', 'omitCommas' => true], + 'weekends' => true, + 'slotDuration' => '00:30:00' + ], + ], + 'defaultView' => 'timeGridWeek', + 'timeGridEventMinHeight' => 20, + 'eventSources' => [ + [ + 'url' => $this->conflictsURL($selection_id), + 'method' => 'GET', + 'extraParams' => [] + ] + ], + 'nowIndicator' => false + ], + ['class' => 'resource-plan semester-plan'] ); - foreach (array_reverse(Semester::getAll()) as $semester) { - $widget->addElement(new SelectElement( - $semester->id, - $semester->name, - $semester->id === $this->selected_semester->id - ), 'sem_select-' . $semester->id - ); + + // get selected StgteilVersions colors + $this->selections = MvvOverlappingSelection::findBySQL( + '`selection_id` = ? ORDER BY `comp_version_id`', + [$selection_id] + ); + } + + /** + * Retrieves all conflicts for the given selection. + * + * @param $selection_id The id of the selection. + * @return void + */ + public function conflicts_action($selection_id) + { + $selections = MvvOverlappingSelection::findBySQL( + '`selection_id` = ? ORDER BY `comp_version_id`', + [$selection_id] + ); + $conflicting_metadates = []; + foreach ($selections as $selection) { + foreach ($selection->conflicts as $conflict) { + $event_data = $this->createEventFromConflict($conflict, true); + $base_index = $conflict->base_course->id . $event_data->begin->getTimestamp(); + $conflicting_metadates[$base_index] = $event_data->toFullcalendarEvent(); + $event_data = $this->createEventFromConflict($conflict); + $comp_index = $conflict->comp_course->id . $event_data->begin->getTimestamp(); + $conflicting_metadates[$comp_index] = $event_data->toFullcalendarEvent(); + } } - $sidebar->addWidget($widget); + $this->render_json(array_values($conflicting_metadates)); } /** - * Search for base version by given search term. + * Shows a serialized view of the conflict. + * + * @param string $conflict_id The id of the conflict. + * @return void */ - public function base_version_action() + public function course_conflict_action(string $conflict_id) { - $sword = Request::get('term'); - $this->render_text(json_encode($this->getResult($sword))); + $this->conflict = MvvOverlappingConflict::find($conflict_id); + if (empty($this->conflict)) { + throw new InvalidArgumentException(); + } + $this->conflicts = SimpleORMapCollection::createFromArray([$this->conflict]); + $this->base_version = $this->conflict->base_abschnitt->version; + $this->version = $this->conflict->comp_abschnitt->version; + $this->course = $this->conflict->comp_course; + $this->selected_view = 'conflict'; } /** - * Search für comparison version by given search term. + * Shows the conflict in a dialog. + * + * @param string $conflict_id The id of the conflict. + * @return void + * @throws \Flexi\TemplateNotFoundException + * @throws \Trails\Exceptions\DoubleRenderError */ - public function comp_versions_action() + public function conflict_action(string $conflict_id) { - $sword = Request::get('term'); - $version_id = Config::get()->MVV_OVERLAPPING_SHOW_VERSIONS_INSIDE_MULTIPLE_STUDY_COURSES - ? Request::option('version_id') - : null; - $version_ids = $this->getRelatedVersions($version_id); - $this->render_text(json_encode($this->getResult($sword, $version_ids))); + $this->conflict = MvvOverlappingConflict::find($conflict_id); + if (empty($this->conflict)) { + throw new InvalidArgumentException(); + } + $this->version = $this->conflict->comp_abschnitt->version; + $this->course = $this->conflict->comp_course; + PageLayout::setTitle($this->course->getFullName( + Config::get()->IMPORTANT_SEMNUMBER + ? 'number-type-name' + : 'type-name' + ) + ); + $this->content = ''; + if (empty($this->conflict->comp_course)) { + PageLayout::postError(_('Unbekannte Veranstaltung.')); + } else { + Request::set('sem_id', $this->conflict->comp_course_id); + $this->course = $this->conflict->comp_course; + $this->version = $this->conflict->comp_abschnitt->version; + $response = $this->relayWithRedirect('course/details/index'); + $this->content = $response->body; + } + $this->selected_view = 'conflict'; + $this->render_template('admin/overlapping/info_dialog'); } /** - * Returns versions related to the base version. + * Creates EventData from conflicts. * - * @param type $version_id - * @return type + * @param MvvOverlappingConflict $conflict The conflict object. + * @return \Studip\Calendar\EventData The event data. */ - private function getRelatedVersions($version_id) + private function createEventFromConflict(MvvOverlappingConflict $conflict, $base = false): \Studip\Calendar\EventData { - $version_ids = []; - $version = StgteilVersion::find($version_id); - if ($version) { - $studiengaenge = Studiengang::findByStgTeil($version->stgteil_id); + static $color_mapping = []; + + $weekday_mapping =[ + 1 => 'mon', + 2 => 'tue', + 3 => 'wed', + 4 => 'thu', + 5 => 'fri', + 6 => 'sat', + 7 => 'sun', + ]; + if ($base) { + $version = $conflict->selection->comp_version; + $col_version_id = $conflict->selection->base_version->id; + $cycle = $conflict->base_cycle; + $course = $conflict->base_course; } else { - return null; + $version = $conflict->selection->base_version; + $col_version_id = $conflict->selection->comp_version->id; + $cycle = $conflict->comp_cycle; + $course = $conflict->comp_course; } - foreach ($studiengaenge as $studiengang) { - if ($studiengang->typ == 'mehrfach') { - foreach ($studiengang->studiengangteile as $studiengangteil) { - $version_ids = array_merge( - $version_ids, - $studiengangteil->versionen->pluck('version_id') - ); - } - } + if (empty($color_mapping[$col_version_id])) { + $color_mapping[$col_version_id] = count($color_mapping) + 1; } - return count($version_ids) ? array_diff($version_ids, [$version_id]) : null; + $color_pos = $color_mapping[$col_version_id]; + $text_color = Config::get()->PERS_TERMIN_KAT[$color_pos]['fgcolor']; + $background_color = Config::get()->PERS_TERMIN_KAT[$color_pos]['bgcolor']; + $border_color = Config::get()->PERS_TERMIN_KAT[$color_pos]['border_color']; + $begin = new DateTime(); + $begin->setTimestamp($this->selected_semester->vorles_beginn); + $begin->modify( + $weekday_mapping[$cycle->weekday] + . ' this week ' + . $cycle->start_time + ); + $end = clone $begin; + $end->modify('today ' . $cycle->end_time); + return new \Studip\Calendar\EventData( + $begin, + $end, + Config::get()->IMPORTANT_SEMNUMBER + ? $course->getFullName('number-type-name') + : $course->getFullName('type-name'), + ['user-date', 'user-date-category1'], + $text_color ?? '#ffffff', + $background_color ?? '#000000', + false, + 'MvvOverlappingConflict', + $conflict->id, + 'MvvOverlappingSelection', + $conflict->selection->id, + 'user', + $conflict->selection->user_id, + [ + 'show' => $this->course_conflictURL($conflict->id) + ], + [], + '', + $border_color ?? '#ffffff' + ); } /** - * Search for studiengangteil versionen by given keyword. The result can be - * filtered by version ids. + * Init the sidebar content. * - * @param string $keyword The keyword to search for. - * @param array $version_ids An array of version ids. - * @return array An array of studiengangteil versionen. + * @return void */ - private function getResult($keyword, $version_ids = null) { - $version_query = ''; + private function setSidebar() + { + $sidebar = Sidebar::Get(); - if (!is_null($version_ids)) { - $version_query = ' AND `mvv_stgteilversion`.`version_id` IN (:version_ids) '; - } + $views = new ViewsWidget(); + $views->addLink( + _('Listenansicht'), + $this->indexURL() + )->setActive($this->view === 'index'); + $views->addLink( + _('Planeransicht'), + $this->planerURL() + )->setActive($this->view === 'planer'); + $sidebar->addWidget($views); - $query = "SELECT `version_id`, `fach`.`name`, `mvv_stgteil`.`kp` - FROM `fach` - INNER JOIN `mvv_stgteil` USING(`fach_id`) - INNER JOIN `mvv_stgteilversion` USING(`stgteil_id`) - INNER JOIN `semester_data` AS `start_sem` - ON (`mvv_stgteilversion`.`start_sem` = `start_sem`.`semester_id`) - LEFT JOIN `semester_data` AS `end_sem` - ON (`mvv_stgteilversion`.`end_sem` = `end_sem`.`semester_id`) - WHERE (`fach`.`name` LIKE :keyword - OR `mvv_stgteil`.`zusatz` LIKE :keyword - OR `mvv_stgteilversion`.`code` LIKE :keyword) + $semester_selector = new SelectWidget( + _('Semesterauswahl'), + $this->url_for('admin/overlapping/reset'), + 'sem_select' + ); + foreach (array_reverse(Semester::getAll()) as $semester) { + $semester_selector->addElement(new SelectElement( + $semester->id, + $semester->name, + $semester->id === $this->selected_semester->id + ), 'sem_select-' . $semester->id + ); + } + $sidebar->addWidget($semester_selector); + } - AND (`start_sem`.`beginn` <= :sem_end - OR ISNULL(`start_sem`.`beginn`)) - AND (`end_sem`.`ende` >= :sem_start - OR ISNULL(`end_sem`.`ende`)) - " . $version_query . " - ORDER BY `name` ASC, `kp` ASC"; + /** + * Search for base version by given search term. + */ + public function base_version_action() + { + $sword = Request::get('term'); + $this->render_text(json_encode($this->getResult($sword))); + } - $stat = array_keys(array_filter( + /** + * Get Studiengangteilversionen for selection, filtered by start and end semester and status (only public). + * + * @return StgteilVersion[] + */ + private function getStgteilVersions(): array + { + // get public status from config + $public_status = array_keys(array_filter( $GLOBALS['MVV_STGTEILVERSION']['STATUS']['values'], function ($v) { return $v['public']; } )); - $stmt = DBManager::get()->prepare($query); - $stmt->execute([ - ':keyword' => '%' . $keyword . '%', - ':stat' => $stat, - ':sem_start' => $this->selected_semester->beginn, - ':sem_end' => $this->selected_semester->ende, - ':version_ids' => $version_ids - ]); - $res = ['results' => []]; - foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $version_id) { - $version = StgteilVersion::find($version_id); - $res['results'][] = [ - 'id' => $version->id, - 'text' => $version->getDisplayName() - ]; - } - return $res; + return StgteilVersion::findBySQL( + "JOIN `mvv_stgteil` USING(`stgteil_id`) + JOIN `fach` USING(`fach_id`) + JOIN `semester_data` AS `start_sem` + ON `mvv_stgteilversion`.`start_sem` = `start_sem`.`semester_id` + LEFT JOIN `semester_data` AS `end_sem` + ON `mvv_stgteilversion`.`end_sem` = `end_sem`.`semester_id` + WHERE (`start_sem`.`beginn` <= :sem_end) + AND (`end_sem`.`ende` >= :sem_start OR ISNULL(`end_sem`.`ende`)) + AND `mvv_stgteilversion`.`stat` IN (:status) + ORDER BY `fach`.`name`, `mvv_stgteil`.`kp`", + [ + ':sem_start' => $this->selected_semester->beginn, + ':sem_end' => $this->selected_semester->ende, + ':status' => $public_status + ] + ); } } diff --git a/app/controllers/search/studiengaenge.php b/app/controllers/search/studiengaenge.php index e17eb2a9773c3a8d93c2738dabaf79be22d5b9f5..0dd5c7d9f1a7af22ab64ab27d2fcd4fa1a691d4c 100644 --- a/app/controllers/search/studiengaenge.php +++ b/app/controllers/search/studiengaenge.php @@ -364,7 +364,9 @@ class Search_StudiengaengeController extends MVVController Sidebar::get()->addWidget($widget, 'mhb_export'); } - $this->breadcrumb->append($this->studiengang, 'studiengang'); + if ($this->breadcrumb) { + $this->breadcrumb->append($this->studiengang, 'studiengang'); + } $this->render_template('search/studiengaenge/verlauf', $this->layout); } diff --git a/app/views/admin/overlapping/admin_info.php b/app/views/admin/overlapping/admin_info.php index 1eeac77f6ba1fef711551c29aafecda380e62058..7dbd17959484106a3842dad77eb8a126b06ac663 100644 --- a/app/views/admin/overlapping/admin_info.php +++ b/app/views/admin/overlapping/admin_info.php @@ -32,3 +32,4 @@ </dl> </section> </section> +<?= $this->render_partial('admin/overlapping/buttons') ?> diff --git a/app/views/admin/overlapping/buttons.php b/app/views/admin/overlapping/buttons.php new file mode 100644 index 0000000000000000000000000000000000000000..64c30822992c48c2a60dfe1aa0a4bb0a690fde01 --- /dev/null +++ b/app/views/admin/overlapping/buttons.php @@ -0,0 +1,46 @@ +<?php +/** + * @var string $selected_view + * @var Admin_OverlappingController $controller + * @var MvvOverlappingConflict $conflict + * @var Course $course + * @var StgteilVersion $version + */ +?> +<div data-dialog-button> + <? if ($selected_view !== 'info') : ?> + <?= \Studip\LinkButton::create( + _('Studienverlaufsplan'), + $controller->version_infoURL($conflict->id), + ['data-dialog' => 'size=auto;reload-on-close'] + ) ?> + <? endif ?> + <? if ($course) : ?> + <?= Studip\LinkButton::create( + _('Ausblenden'), + $controller->excludeURL($conflict->id), + ['data-dialog' => 'size=auto;reload-on-close'] + ) ?> + <? if ($selected_view !== 'course_info') : ?> + <?= \Studip\LinkButton::create( + _('Veranstaltungsdetails'), + $controller->course_infoURL($conflict->id), + ['data-dialog' => 'size=auto;reload-on-close'] + ) ?> + <? endif ?> + <? if ($selected_view !== 'admin_info') : ?> + <?= \Studip\LinkButton::create( + _('Kontakt'), + $controller->admin_infoURL($conflict->id), + ['data-dialog' => 'size=auto;reload-on-close'] + ) ?> + <? endif ?> + <? if ($selected_view !== 'conflict') : ?> + <?= \Studip\LinkButton::create( + _('Konflikt'), + $controller->course_conflictURL($conflict->id), + ['data-dialog' => 'size=auto;reload-on-close'] + ) ?> + <? endif ?> + <? endif ?> +</div> diff --git a/app/views/admin/overlapping/conflicts.php b/app/views/admin/overlapping/conflicts.php index 6d22c1df2b9c4f06ae5f929e59c2cf5508b221ce..f1cd914f17e7047d64524827f5163243a2d0d033 100644 --- a/app/views/admin/overlapping/conflicts.php +++ b/app/views/admin/overlapping/conflicts.php @@ -12,95 +12,102 @@ <? $comp_versions = StgteilVersion::findBySQL('INNER JOIN `mvv_stgteilabschnitt` USING (`version_id`) WHERE `abschnitt_id` IN (?) GROUP BY `version_id`', [$comp_abs]); ?> <? foreach ($comp_versions as $comp_version) : ?> <li> - <?= Icon::create('category', Icon::ROLE_INFO); ?> - <a class="mvv-ovl-version" href="<?= $controller->url_for('admin/overlapping/version_info', $comp_version->id) ?>" data-dialog="size=auto"> - <?= htmlReady($comp_version->getDisplayName()); ?> - </a> + <div class="mvv-ovl-title"> + <?= Icon::create('category', Icon::ROLE_INFO); ?> + <a href="<?= $controller->version_info($comp_version->id) ?>" data-dialog="size=auto"> + <?= htmlReady($comp_version->getDisplayName()); ?> + </a> + </div> <? $comp_abschnitte = $comp_version->abschnitte->findBy('abschnitt_id', $conflicts->findBy('base_metadate_id', $cycle->id)->pluck('comp_abschnitt_id'))->orderBy('position', SORT_NUMERIC) ?> <ul> <? foreach ($comp_abschnitte as $comp_abschnitt) : ?> <li> - <?= htmlReady($comp_abschnitt->getDisplayName()) ?> - <? $modul_ids = Modulteil::findAndMapMany(function ($mt) { - return $mt->modul_id; - }, $conflicts->findBy('base_metadate_id', $cycle->id)->pluck('comp_modulteil_id')) ?> - <? $module = StgteilabschnittModul::findBySQL( - '`abschnitt_id` = ? AND `modul_id` IN (?) ORDER BY `position`', - [$comp_abschnitt->id, $modul_ids] - ) ?> - <ul> - <? foreach ($module as $modul) : ?> - <li> - <?= Icon::create('log', Icon::ROLE_INFO); ?> - <?= htmlReady($modul->getDisplayName()) ?> - <? $conflicts_modulteile = $conflicts->filter(function ($c) use ($cycle, $comp_abschnitt) { - return $c->base_metadate_id == $cycle->id && $c->comp_abschnitt_id == $comp_abschnitt->id; - }) ?> - <? $comp_modulteile = $modul->modul->modulteile->findBy('modulteil_id', $conflicts_modulteile->pluck('comp_modulteil_id')) ?> - <ul> - <? foreach ($comp_modulteile as $comp_modulteil) : ?> - <li class="mvv-ovl-comp-modulteil"> - <? $id = md5($base_modul->abschnitt_id . $comp_abschnitt->id . $comp_modulteil->id) ?> - <input id="<?= $id ?>" type="checkbox" checked> - <label for="<?= $id ?>"></label> - <div> - <?= htmlReady($comp_modulteil->getDisplayName()) ?> - </div> - <div> - <? foreach (range(1, 6) as $fachsem_nr) : ?> - <? $fachsems = $comp_modulteil->abschnitt_assignments->findBy('abschnitt_id', $comp_abschnitt->id); ?> - <? $fachsem = $fachsems->findOneBy('fachsemester', $fachsem_nr); ?> - <? if ($fachsem) : ?> - <div <?= tooltip($GLOBALS['MVV_MODULTEIL_STGABSCHNITT']['STATUS']['values'][$fachsem->differenzierung]['name']) ?>> - <?= $GLOBALS['MVV_MODULTEIL_STGABSCHNITT']['STATUS']['values'][$fachsem->differenzierung]['icon']; ?> - <? else : ?> - <div> - <? endif; ?> + <div class="mvv-ovl-title"> + <?= htmlReady($comp_abschnitt->getDisplayName()) ?> + </div> + <? $modul_ids = Modulteil::findAndMapMany(function ($mt) { + return $mt->modul_id; + }, $conflicts->findBy('base_metadate_id', $cycle->id)->pluck('comp_modulteil_id')) ?> + <? $module = StgteilabschnittModul::findBySQL( + '`abschnitt_id` = ? AND `modul_id` IN (?) ORDER BY `position`', + [$comp_abschnitt->id, $modul_ids] + ) ?> + <ul> + <? foreach ($module as $modul) : ?> + <li> + <div class="mvv-ovl-title"> + <?= Icon::create('log', Icon::ROLE_INFO); ?> + <?= htmlReady($modul->getDisplayName()) ?> + </div> + <? $conflicts_modulteile = $conflicts->filter(function ($c) use ($cycle, $comp_abschnitt) { + return $c->base_metadate_id == $cycle->id && $c->comp_abschnitt_id == $comp_abschnitt->id; + }) ?> + <? $comp_modulteile = $modul->modul->modulteile->findBy('modulteil_id', $conflicts_modulteile->pluck('comp_modulteil_id')) ?> + <ul> + <? foreach ($comp_modulteile as $comp_modulteil) : ?> + <li class="mvv-ovl-modulteil"> + <? $id = md5($base_modul->abschnitt_id . $comp_abschnitt->id . $comp_modulteil->id) ?> + <input id="<?= $id ?>" type="checkbox" checked> + <label for="<?= $id ?>"></label> + <div> + <?= htmlReady($comp_modulteil->getDisplayName()) ?> </div> - <? endforeach; ?> - </div> - <? $comp_cycles = $conflicts->filter(function ($c) use ($comp_abschnitt, $comp_modulteil, $cycle, $base_modul, $modulteil) { - return ($c->base_abschnitt_id == $base_modul->abschnitt_id - && $c->base_modulteil_id == $modulteil->id - && $c->base_metadate_id == $cycle->id - && $c->comp_abschnitt_id == $comp_abschnitt->id - && $c->comp_modulteil_id == $comp_modulteil->id); - }); ?> - <ul class="mvv-overlapping-conflicts"> - <? foreach ($comp_cycles as $comp_cycle) : ?> - <li> - <?= htmlReady($comp_cycle->comp_course->VeranstaltungsNummer) ?> - <a href="<?= $controller->url_for('admin/overlapping/course_info', $comp_cycle->comp_course_id) ?>" data-dialog=""> - <?= Icon::create('info-circle', Icon::ROLE_INFO, ['style' => 'vertical-align: text-bottom;', 'title' => _('Veranstaltungsdetails')]) ?> - </a> - <?= htmlReady($comp_cycle->comp_course->getFullName('type-name')) ?> - <? if ($comp_cycle->comp_course->admission_turnout) : ?> - <?= sprintf(_('(erw. TN %s)'), htmlReady($comp_cycle->comp_course->admission_turnout)) ?> + <div> + <? foreach (range(1, 6) as $fachsem_nr) : ?> + <? $fachsems = $comp_modulteil->abschnitt_assignments->findBy('abschnitt_id', $comp_abschnitt->id); ?> + <? $fachsem = $fachsems->findOneBy('fachsemester', $fachsem_nr); ?> + <? if ($fachsem) : ?> + <div <?= tooltip($GLOBALS['MVV_MODULTEIL_STGABSCHNITT']['STATUS']['values'][$fachsem->differenzierung]['name']) ?>> + <?= $GLOBALS['MVV_MODULTEIL_STGABSCHNITT']['STATUS']['values'][$fachsem->differenzierung]['icon']; ?> + <? else : ?> + <div> <? endif; ?> - <? $dates = $comp_cycle->comp_cycle->dates->filter(function ($c) use ($selected_semester) { - return ($selected_semester->beginn <= $c->date && $selected_semester->ende >= $c->date); - }); ?> - <?= Icon::create('date-cycle', Icon::ROLE_INFO, ['style' => 'vertical-align: text-bottom;']) ?> - <?= sprintf('%s (%sx)', $comp_cycle->comp_cycle->toString('short'), count($dates)); ?> - <a href="<?= $controller->url_for('admin/overlapping/admin_info', $comp_cycle->comp_course->id) ?>" data-dialog="size=auto;title='<?= htmlReady($comp_cycle->comp_course->getFullName()) ?>';"> - <?= Icon::create('person-online', Icon::ROLE_INFO, ['style' => 'vertical-align: text-bottom;', 'title' => _('Zuständige Administratoren')]) ?> - </a> - <span title="<?= $comp_cycle->isExcluded() ? _('Veranstaltung nicht berücksichtigen') : _('Veranstaltung berücksichtigen') ?>" - data-mvv-ovl-course="<?= $comp_cycle->comp_course_id ?>" - data-mvv-ovl-selection="<?= $comp_cycle->selection_id ?>" - class="mvv-overlapping-exclude<?= $comp_cycle->isExcluded() ? ' mvv-overlapping-invisible' : '' ?>"> - </span> - </li> - <? endforeach; ?> - </ul> - </li> - <? endforeach; ?> - </ul> - </li> - <? endforeach; ?> - </ul> - </li> + </div> + <? endforeach; ?> + </div> + <? $comp_cycles = $conflicts->filter(function ($c) use ($comp_abschnitt, $comp_modulteil, $cycle, $base_modul, $modulteil) { + return ($c->base_abschnitt_id == $base_modul->abschnitt_id + && $c->base_modulteil_id == $modulteil->id + && $c->base_metadate_id == $cycle->id + && $c->comp_abschnitt_id == $comp_abschnitt->id + && $c->comp_modulteil_id == $comp_modulteil->id); + }); ?> + <ul> + <? foreach ($comp_cycles as $comp_cycle) : ?> + <li> + <div class="mvv-ovl-title"> + <?= htmlReady($comp_cycle->comp_course->VeranstaltungsNummer) ?> + <a href="<?= $controller->course_info($comp_cycle->id) ?>" data-dialog> + <?= Icon::create('info-circle', Icon::ROLE_INFO)->asImg(['style' => 'vertical-align: text-bottom;', 'title' => _('Veranstaltungsdetails')]) ?> + </a> + <?= htmlReady($comp_cycle->comp_course->getFullName('type-name')) ?> + <? if ($comp_cycle->comp_course->admission_turnout) : ?> + <?= sprintf(_('(erw. TN %s)'), htmlReady($comp_cycle->comp_course->admission_turnout)) ?> + <? endif; ?> + <? $dates = $comp_cycle->comp_cycle->dates->filter(function ($c) use ($selected_semester) { + return ($selected_semester->beginn <= $c->date && $selected_semester->ende >= $c->date); + }); ?> + <?= Icon::create('date-cycle', Icon::ROLE_INFO, ['style' => 'vertical-align: text-bottom;']) ?> + <?= sprintf('%s (%sx)', $comp_cycle->comp_cycle->toString('short'), count($dates)); ?> + <a href="<?= $controller->admin_info($comp_cycle->id) ?>" data-dialog="size=auto;title='<?= htmlReady($comp_cycle->comp_course->getFullName()) ?>';"> + <?= Icon::create('person-online', Icon::ROLE_INFO, ['style' => 'vertical-align: text-bottom;', 'title' => _('Zuständige Administratoren')]) ?> + </a> + <span title="<?= $comp_cycle->isExcluded() ? _('Veranstaltung nicht berücksichtigen') : _('Veranstaltung berücksichtigen') ?>" + data-mvv-ovl-conflict="<?= $comp_cycle->id ?>" + class="mvv-overlapping-exclude<?= $comp_cycle->isExcluded() ? ' mvv-overlapping-invisible' : '' ?>"> + </span> + </div> + </li> + <? endforeach; ?> + </ul> + </li> + <? endforeach; ?> + </ul> + </li> + <? endforeach; ?> + </ul> + </li> <? endforeach; ?> </ul> </li> -<? endforeach; ?> +<? endforeach; ?> diff --git a/app/views/admin/overlapping/course_conflict.php b/app/views/admin/overlapping/course_conflict.php new file mode 100644 index 0000000000000000000000000000000000000000..345d4ba402d5fa2c8ff6510ae9e303ea15a339f5 --- /dev/null +++ b/app/views/admin/overlapping/course_conflict.php @@ -0,0 +1,2 @@ +<?= $this->render_partial('admin/overlapping/overlapping') ?> +<?= $this->render_partial('admin/overlapping/buttons') ?> diff --git a/app/views/admin/overlapping/courses.php b/app/views/admin/overlapping/courses.php index aa2e83f66f81fda3fea5e6f19cf09068a6741a9d..5cb0a7d949405e4d83c1102f91095824b13e383b 100644 --- a/app/views/admin/overlapping/courses.php +++ b/app/views/admin/overlapping/courses.php @@ -15,23 +15,24 @@ return ($selected_semester->beginn <= $c->date && $selected_semester->ende >= $c->date); }); ?> <li> - <div class="mvv-ovl-base-course">!</div> <? $id = md5($modul->abschnitt_id . $modulteil->id . $course['seminar_id']) ?> <input id="<?= $id ?>" type="checkbox" checked> - <label for="<?= $id ?>"></label> - <?= htmlReady($course_obj->VeranstaltungsNummer) ?> - <a href="<?= $controller->url_for('admin/overlapping/course_info', $course_obj->id) ?>" data-dialog=""> - <?= Icon::create('info-circle', Icon::ROLE_INFO, [ - 'class' => 'text-bottom', - 'title' => _('Veranstaltungsdetails') - ]) ?> - </a> - <?= htmlReady($course_obj->getFullName('type-name')) ?> - <? if ($course_obj->admission_turnout) : ?> - <?= sprintf(_('(erw. TN %s)'), htmlReady($course_obj->admission_turnout)) ?> - <? endif; ?> - <?= Icon::create('date-cycle', Icon::ROLE_INFO, ['class' => 'text-bottom']) ?> - <?= sprintf('%s (%sx)', $cycle->toString('short'), count($dates)); ?> + <div class="mvv-ovl-title"> + <label for="<?= $id ?>"></label> + <?= htmlReady($course_obj->VeranstaltungsNummer) ?> + <a href="<?= $controller->course_info($course_obj->id) ?>" data-dialog=""> + <?= Icon::create('info-circle', Icon::ROLE_INFO, [ + 'class' => 'text-bottom', + 'title' => _('Veranstaltungsdetails') + ]) ?> + </a> + <?= htmlReady($course_obj->getFullName('type-name')) ?> + <? if ($course_obj->admission_turnout) : ?> + <?= sprintf(_('(erw. TN %s)'), htmlReady($course_obj->admission_turnout)) ?> + <? endif; ?> + <?= Icon::create('date-cycle', Icon::ROLE_INFO)->asImg(['class' => 'text-bottom']) ?> + <?= sprintf('%s (%sx)', $cycle->toString('short'), count($dates)); ?> + </div> <ul> <?= $this->render_partial('admin/overlapping/conflicts', ['cycle' => $cycle, 'base_modul' => $modul, 'selected_semester' => $selected_semester]) ?> </ul> diff --git a/app/views/admin/overlapping/index.php b/app/views/admin/overlapping/index.php index 892a198b9265234dd724944e21aa74d307b1d4b9..7736186102150e6cf4977a04847fa9c2cf704f86 100644 --- a/app/views/admin/overlapping/index.php +++ b/app/views/admin/overlapping/index.php @@ -3,12 +3,11 @@ * @var SimpleORMapCollection $conflicts * @var array $semtypes * @var array $fachsems + * @var array $stgteil_versions + * @var string $fullcalendar */ ?> <?= $this->render_partial('admin/overlapping/selection', ['fachsems' => $fachsems, 'semtypes' => $semtypes]) ?> <? if (count($conflicts)) : ?> <?= $this->render_partial('admin/overlapping/overlapping') ?> <? endif; ?> -<script> - STUDIP.Overlapping.init(); -</script> diff --git a/app/views/admin/overlapping/info_dialog.php b/app/views/admin/overlapping/info_dialog.php new file mode 100644 index 0000000000000000000000000000000000000000..63f6c2577fad1e9d92a3e835491a9692ca03f495 --- /dev/null +++ b/app/views/admin/overlapping/info_dialog.php @@ -0,0 +1,7 @@ +<?php +/** + * @var string $content + */ +?> +<?= $content ?> +<?= $this->render_partial('admin/overlapping/buttons') ?> diff --git a/app/views/admin/overlapping/modul.php b/app/views/admin/overlapping/modul.php index fe9d4832423a9b2d5afc068ea157d98caa623b85..aaf31d2c9556eebc3b6d7e1a06dd418e8ebca5a5 100644 --- a/app/views/admin/overlapping/modul.php +++ b/app/views/admin/overlapping/modul.php @@ -5,10 +5,13 @@ * @var StgteilAbschnitt $abschnitt */ ?> -<?= Icon::create('log', Icon::ROLE_INFO); ?> <?= htmlReady($modul->getDisplayName()); ?> +<div class="mvv-ovl-title"> + <?= Icon::create('log', Icon::ROLE_INFO); ?> + <?= htmlReady($modul->getDisplayName()); ?> +</div> <ul> <? foreach ($modul->modul->modulteile->findBy('id', $conflicts->pluck('base_modulteil_id')) as $modulteil) : ?> - <li class="mvv-ovl-base-modulteil"> + <li class="mvv-ovl-modulteil"> <? $id = md5($modul->abschnitt_id . $modulteil->id) ?> <input id="<?= $id ?>" type="checkbox" checked> <label for="<?= $id ?>"></label> diff --git a/app/views/admin/overlapping/overlapping.php b/app/views/admin/overlapping/overlapping.php index 328122a27836c9a33d953b58e0bcc112150e93df..5d5c96604ea7588d6221874f1338d8cdbc085111 100644 --- a/app/views/admin/overlapping/overlapping.php +++ b/app/views/admin/overlapping/overlapping.php @@ -4,10 +4,14 @@ * @var StgteilVersion $base_version */ ?> -<h1> - <?= Icon::create('category', Icon::ROLE_INFO)->asImg() ?> - <?= htmlReady($base_version->getDisplayName()); ?> -</h1> +<? if (Request::isXhr()) : ?> + <? PageLayout::setTitle($base_version->getDisplayName()) ?> +<? else : ?> + <h1> + <?= Icon::create('category', Icon::ROLE_INFO) ?> + <?= htmlReady($base_version->getDisplayName()); ?> + </h1> +<? endif ?> <section> <? foreach ($base_version->abschnitte->findBy('id', $conflicts->pluck('base_abschnitt_id')) as $abschnitt) : ?> <article> diff --git a/app/views/admin/overlapping/planer.php b/app/views/admin/overlapping/planer.php new file mode 100644 index 0000000000000000000000000000000000000000..bf71e73341195513d2cb0c051390857faac7a598 --- /dev/null +++ b/app/views/admin/overlapping/planer.php @@ -0,0 +1,27 @@ +<?php +/** + * @var Studip\Fullcalendar $fullcalendar + * @var array $selections + */ +?> +<?= $fullcalendar ?> +<? if (count($selections) > 0) : ?> + <ul class="map-key-list"> + <? $color_index = 1 ?> + <li class="map-key"> + <span style="background-color:<?= Config::get()->PERS_TERMIN_KAT[$color_index++]['bgcolor'] ?>"> + + </span> + <?= htmlReady($selections[0]->base_version->getDisplayName()) ?> + </li> + <? foreach ($selections as $selection) : ?> + <? if ($selection->base_version->id !== $selection->comp_version->id) : ?> + <li class="map-key"> + <span style="background-color:<?= Config::get()->PERS_TERMIN_KAT[$color_index++]['bgcolor'] ?>"></span> + <?= htmlReady($selection->comp_version->getDisplayName()) ?> + </li> + <? endif ?> + <? endforeach ?> + </ul> +<? endif ?> + diff --git a/app/views/admin/overlapping/selection.php b/app/views/admin/overlapping/selection.php index 18b1aef01e09633b014d52a317490fd20ce236b2..11c6208a31c451840a3a55907ebc247874e47cea 100644 --- a/app/views/admin/overlapping/selection.php +++ b/app/views/admin/overlapping/selection.php @@ -5,39 +5,41 @@ * @var array $semtypes * @var StgteilVersion $base_version * @var StgteilVersion[] $comp_versions + * @var StgteilVersion[] $stgteil_versions + * @var string $base_version_id + * @var array $comp_versions_ids */ ?> -<form method="post" class="default collapsable mvv-ovl-selection" action="<?= $controller->url_for('admin/overlapping/check') ?>"> +<form method="post" class="default collapsable mvv-ovl-selection" action="<?= $controller->checkURL() ?>"> <fieldset> <legend> <?= _('Auswahl') ?> </legend> - - <label for="base-version-select"> - <span><?= _('Studiengangteil') ?></span> - <select id="base-version-select" class="nested-select" name="base_version"> - <? if ($base_version) : ?> - <option value="<?= $base_version->id ?>" selected><?= htmlReady($base_version->getDisplayName()) ?></option> - <? endif; ?> + <label> + <span class="required"><?= _('Studiengangteil') ?></span> + <select class="nested-select" name="base_version" required> + <? foreach($stgteil_versions as $stgteil_version) : ?> + <option + value="<?= $stgteil_version->id ?>" + <?= $stgteil_version->id === $base_version_id ? 'selected' : '' ?> + ><?= htmlReady($stgteil_version->getDisplayName()) ?></option> + <? endforeach ?> </select> </label> - - - <label for="comp-versions-select"> - <span><?= _('Vergleichs-Studiengangteile') ?></span> - <select id="comp-versions-select" class="nested-select" name="comp_versions[]" multiple> - <? if (count($comp_versions)) : ?> - <? foreach($comp_versions as $comp_version) : ?> - <option value="<?= $comp_version->id ?>" selected><?= htmlReady($comp_version->getDisplayName()) ?></option> - <? endforeach; ?> - <? endif; ?> + <label> + <?= _('Vergleichs-Studiengangteile') ?> + <select class="nested-select" name="comp_versions[]" multiple> + <? foreach ($stgteil_versions as $stgteil_version) : ?> + <option + value="<?= htmlReady($stgteil_version->id) ?>" + <?= in_array($stgteil_version->id, $comp_versions_ids) ? 'selected' : '' ?> + ><?= htmlReady($stgteil_version->getDisplayName()) ?></option> + <? endforeach ?> </select> </label> - - - <label for="fachsem-select"> + <label> <span><?= _('Fachsemester') ?></span> - <select id="fachsem-select" class="nested-select" name="fachsems[]" multiple> + <select class="nested-select" name="fachsems[]" multiple> <? foreach (range(1, 6) as $fsem) : ?> <option value="<?= $fsem ?>"<?= in_array($fsem, (array) $fachsems) ? ' selected' : '' ?>> <?= $fsem . ModuleManagementModel::getLocaleOrdinalNumberSuffix($fsem) . ' ' . _('Fachsemester') ?> @@ -45,10 +47,9 @@ <? endforeach; ?> </select> </label> - - <label for="semtype-select"> - <span><?= _('Veranstaltungstyp-Filter') ?></span> - <select id="semtype-select" class="nested-select" name="semtypes[]" multiple> + <label> + <?= _('Veranstaltungstyp-Filter') ?> + <select id="semtype-select_" class="nested-select" name="semtypes[]" multiple> <? foreach ($GLOBALS['SEM_CLASS'] as $class_id => $class) : ?> <? if ($class['studygroup_mode']) : continue; endif; ?> @@ -63,7 +64,6 @@ <? endforeach; ?> </select> </label> - <label> <input type="checkbox" name="show_hidden" @@ -73,6 +73,6 @@ </fieldset> <footer> <?= \Studip\Button::createAccept(_('Vergleichen'), 'compare') ?> - <?= \Studip\Button::createCancel(_('Zurücksetzen'), 'index', ['formaction' => $controller->action_url('reset')]) ?> + <?= \Studip\Button::createCancel(_('Zurücksetzen'), 'index', ['formaction' => $controller->resetURL()]) ?> </footer> </form> diff --git a/app/views/search/studiengaenge/verlauf.php b/app/views/search/studiengaenge/verlauf.php index 5451b43c872a849224b2c14e87e9a2673cdfd690..4464e66c6e706cd5cccaed4a44d9e753e3453f01 100644 --- a/app/views/search/studiengaenge/verlauf.php +++ b/app/views/search/studiengaenge/verlauf.php @@ -1,6 +1,8 @@ -<div> -<?= $this->render_partial('search/breadcrumb') ?> -</div> +<? if (isset($breadcrumb)) : ?> + <div> + <?= $this->render_partial('search/breadcrumb') ?> + </div> +<? endif ?> <? if ($studiengangTeilName) : ?> <? $max_fachsemester = count($fachsemesterData) ? max($fachsemesterData) : 0 ?> <table class="mvv-modul-details default nohover"> @@ -42,7 +44,6 @@ <? foreach ($abschnitteData as $abschnitt_id => $abschnitt): ?> <? $displayedAbschnittName = false ?> <? $ueberschrift = (mb_strlen($abschnitt['zwischenUeberschrift'])) ?> - <?// if (!$ueberschrift): ?> <? if ($ueberschrift): ?> <tr class="table_header"> <td colspan="<?= $max_fachsemester + 3 ?>"><?= htmlReady($abschnitt['zwischenUeberschrift']) ?></td> diff --git a/lib/models/MvvOverlappingSelection.php b/lib/models/MvvOverlappingSelection.php index 525076ccc1427ffb6e8ec74711084f81df109a88..89a765c90e05d44a2b40f37b9a22b4a042543370 100644 --- a/lib/models/MvvOverlappingSelection.php +++ b/lib/models/MvvOverlappingSelection.php @@ -33,12 +33,7 @@ class MvvOverlappingSelection extends SimpleORMap { - /** - * Configures the model. - * - * @param array $config Configuration - */ - protected static function configure($config = array()) + protected static function configure($config = array()): void { $config['db_table'] = 'mvv_ovl_selections'; $config['belongs_to']['semester'] = [ @@ -78,16 +73,17 @@ class MvvOverlappingSelection extends SimpleORMap * Creates a selection id and stores the selection. * * @throws UnexpectedValueException if there are forbidden NULL values - * @return number|boolean + * @return int|boolean */ - public function store() + public function store(): int|bool { if ($this->isNew() && $this->selection_id == '') { $this->selection_id = self::createSelectionId( $this->base_version, - $this->comp_version, - $this->fachsems, - $this->semtypes + [$this->comp_version], + [$this->fachsems], + [$this->semtypes], + $this->semester_id ); } return parent::store(); @@ -97,14 +93,12 @@ class MvvOverlappingSelection extends SimpleORMap * Sets the given Fachsemester. Expects an array or a comma * separated list of Fachsemester. * - * @param array|string $semtypes + * @param string[] $fachsems */ - public function setFachsemester($fachsems) + public function setFachsemester(array $fachsems): void { - if (is_array($fachsems)) { - sort($fachsems, SORT_NUMERIC); - $fachsems = implode(',', $fachsems); - } + sort($fachsems, SORT_NUMERIC); + $fachsems = implode(',', $fachsems); $this->fachsems = $fachsems; } @@ -112,14 +106,13 @@ class MvvOverlappingSelection extends SimpleORMap * Sets the given course types (semtypes). Expects an array or a comma * separated list of course types. * - * @param array|string $semtypes + * @param string[] $semtypes + * @return void */ - public function setCoursetypes($semtypes) + public function setCourseTypes(array $semtypes): void { - if (is_array($semtypes)) { - sort($semtypes, SORT_NUMERIC); - $semtypes = implode(',', $semtypes); - } + sort($semtypes, SORT_NUMERIC); + $semtypes = implode(',', $semtypes); $this->semtypes = $semtypes; } @@ -131,7 +124,6 @@ class MvvOverlappingSelection extends SimpleORMap */ public function storeConflicts() { - $query = " SELECT DISTINCT `cbase`.`metadate_id` AS `cbase_metadate_id`, `cbase`.`seminar_id` AS `cbase_seminar_id`, @@ -145,14 +137,16 @@ class MvvOverlappingSelection extends SimpleORMap INNER JOIN ( SELECT `mvv_lvgruppe_seminar`.`seminar_id`, `mvv_stgteilabschnitt_modul`.`abschnitt_id`, - `mvv_modulteil`.`modulteil_id` + `mvv_modulteil`.`modulteil_id`, + `semester_courses`.`semester_id` FROM `mvv_stgteilabschnitt` INNER JOIN `mvv_stgteilabschnitt_modul` USING (`abschnitt_id`) INNER JOIN `mvv_modul` USING (`modul_id`) INNER JOIN `mvv_modulteil` USING (`modul_id`) INNER JOIN `mvv_lvgruppe_modulteil` USING (`modulteil_id`) INNER JOIN `mvv_lvgruppe_seminar` USING (`lvgruppe_id`) - INNER JOIN `seminare` USING (`seminar_id`) + INNER JOIN `seminare` ON `mvv_lvgruppe_seminar`.`seminar_id` = `seminare`.`Seminar_id` + INNER JOIN `semester_courses` ON `seminare`.`Seminar_id` = `semester_courses`.`course_id` INNER JOIN `mvv_modulteil_stgteilabschnitt` ON (`mvv_stgteilabschnitt_modul`.`abschnitt_id` = `mvv_modulteil_stgteilabschnitt`.`abschnitt_id` @@ -162,7 +156,6 @@ class MvvOverlappingSelection extends SimpleORMap ON (`mvv_modul`.`start` = `start_sem`.`semester_id`) LEFT JOIN `semester_data` AS `end_sem` ON (`mvv_modul`.`end` = `end_sem`.`semester_id`) - LEFT JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) WHERE `mvv_stgteilabschnitt`.`version_id` = :base_version AND `mvv_modulteil_stgteilabschnitt`.`fachsemester` IN (:fachsem) AND ((`start_sem`.`beginn` < :sem_end OR ISNULL(`start_sem`.`beginn`)) @@ -189,15 +182,16 @@ class MvvOverlappingSelection extends SimpleORMap INNER JOIN ( SELECT `mvv_lvgruppe_seminar`.`seminar_id`, `mvv_stgteilabschnitt_modul`.`abschnitt_id`, - `mvv_modulteil`.`modulteil_id` + `mvv_modulteil`.`modulteil_id`, + `semester_courses`.`semester_id` FROM `mvv_stgteilabschnitt` INNER JOIN `mvv_stgteilabschnitt_modul` USING (`abschnitt_id`) INNER JOIN `mvv_modul` USING (`modul_id`) - INNER JOIN `mv" . - "v_modulteil` USING (`modul_id`) + INNER JOIN `mvv_modulteil` USING (`modul_id`) INNER JOIN `mvv_lvgruppe_modulteil` USING (`modulteil_id`) INNER JOIN `mvv_lvgruppe_seminar` USING (`lvgruppe_id`) - INNER JOIN `seminare` USING (`seminar_id`) + INNER JOIN `seminare` ON `mvv_lvgruppe_seminar`.`seminar_id` = `seminare`.`Seminar_id` + INNER JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) INNER JOIN `mvv_modulteil_stgteilabschnitt` ON (`mvv_stgteilabschnitt_modul`.`abschnitt_id` = `mvv_modulteil_stgteilabschnitt`.`abschnitt_id` @@ -207,7 +201,6 @@ class MvvOverlappingSelection extends SimpleORMap ON (`mvv_modul`.`start` = `start_sem`.`semester_id`) LEFT JOIN `semester_data` AS `end_sem` ON (`mvv_modul`.`end` = `end_sem`.`semester_id`) - LEFT JOIN `semester_courses` ON (`seminare`.`Seminar_id` = `semester_courses`.`course_id`) WHERE `mvv_stgteilabschnitt`.`version_id` = :comp_version AND `mvv_modulteil_stgteilabschnitt`.`fachsemester` IN (:fachsem) AND ((`start_sem`.`beginn` < :sem_end OR ISNULL(`start_sem`.`beginn`)) @@ -217,16 +210,16 @@ class MvvOverlappingSelection extends SimpleORMap ) AS `semcomp` ON (`semcomp`.`seminar_id` = `ccomp`.`seminar_id`) INNER JOIN `mvv_modulteil_stgteilabschnitt` AS `mms1` ON (`mms1`.`abschnitt_id` = `semcomp`.`abschnitt_id` AND `mms1`.`modulteil_id` = `semcomp`.`modulteil_id`) - WHERE `mms1`.`fachsemester` IN ( - SELECT `fachsemester` - FROM `mvv_modulteil_stgteilabschnitt` AS `mms2` - WHERE `mms2`.`abschnitt_id` = `sembase`.`abschnitt_id` - AND `mms2`.`modulteil_id` = `sembase`.`modulteil_id`) + WHERE `mms1`.`fachsemester` IN ( + SELECT `fachsemester` + FROM `mvv_modulteil_stgteilabschnitt` AS `mms2` + WHERE `mms2`.`abschnitt_id` = `sembase`.`abschnitt_id` + AND `mms2`.`modulteil_id` = `sembase`.`modulteil_id`) ORDER BY `cbase_seminar_id`"; // if no filter is set use all types and fachsems - $fachsems = $this->fachsems ? $this->fachsems : implode(',', range(1, 6)); - $semtypes = $this->semtypes ? $this->semtypes : implode(',', array_keys(SemType::getTypes())); + $fachsems = $this->fachsems ?: implode(',', range(1, 6)); + $semtypes = $this->semtypes ?: implode(',', array_keys(SemType::getTypes())); $db = DBManager::get(); $conflicts = $db->fetchAll($query, [ @@ -239,7 +232,6 @@ class MvvOverlappingSelection extends SimpleORMap ':semester_id' => $this->semester->id ]); - $conlicts = []; foreach ($conflicts as $conflict) { $ovl_conflict = new MvvOverlappingConflict(); $ovl_conflict->selection_id = $this->id; @@ -260,10 +252,13 @@ class MvvOverlappingSelection extends SimpleORMap * Returns all conflicts of all selections with the given selection id. * * @param string $selection_id The selection id. - * @param boolean $only_visible Returns only visible conflicts. + * @param bool $only_visible Returns only visible conflicts. * @return SimpleORMapCollection All conflicts of appropriate selections. */ - public static function getConflictsBySelection($selection_id, $only_visible = false) + public static function getConflictsBySelection( + string $selection_id, + bool $only_visible = false + ) : SimpleORMapCollection { $excluded_courses = []; $visible_sql = ''; @@ -287,40 +282,42 @@ class MvvOverlappingSelection extends SimpleORMap /** * Returns a md5 hash over all given parameters. * - * @param string $base_version The id of the base version. - * @param string $comp_versions The id of the compared version. - * @param array|string $fachsems An array or a string with comma separated fachsem numbers. - * @param array|string $semtypes An array or a string with comma separated course types. + * @param StgteilVersion $base_version The id of the base version. + * @param StgteilVersion[] $comp_versions The id of the compared version. + * @param string[] $fachsems An array or a string with comma separated fachsem numbers. + * @param string[] $semtypes An array or a string with comma separated course types. * @param string|null $user_id User id that created the selection (defaults to current user) * @return string The md5 id. */ - public static function createSelectionId($base_version, $comp_versions, $fachsems, $semtypes, string $user_id = null) + public static function createSelectionId( + StgteilVersion $base_version, + array $comp_versions, + array $fachsems, + array $semtypes, + string $semester_id, + string|null $user_id = null) : string { - if (is_array($fachsems)) { - sort($fachsems, SORT_NUMERIC); - $fachsems = implode(',', $fachsems); - } - if (is_array($semtypes)) { - sort($semtypes, SORT_NUMERIC); - $semtypes = implode(',', $semtypes); + sort($fachsems, SORT_NUMERIC); + $fachsems = implode(',', $fachsems); + sort($semtypes, SORT_NUMERIC); + $semtypes = implode(',', $semtypes); + $comp_version_ids = []; + foreach ($comp_versions as $comp_version) { + $comp_version_ids[] = $comp_version->id; } - if (is_array($comp_versions)) { - $comp_version_ids = []; - foreach ($comp_versions as $comp_version) { - $comp_version_ids[] = $comp_version->id; - } - sort($comp_version_ids); - $comp_versions = implode(',', $comp_version_ids); - } else { - $comp_versions = $comp_versions->id; - } - return md5(implode('_', [ - $base_version->id, - $comp_versions, - trim($fachsems) ? $fachsems : 'x', - trim($semtypes) ? $semtypes : 'x', - $user_id ?? $GLOBALS['user']->id, - ])); + sort($comp_version_ids); + $comp_versions = implode(',', $comp_version_ids); + + return md5(implode('_', + [ + $base_version->id, + $comp_versions, + trim($fachsems) ? $fachsems : 'x', + trim($semtypes) ? $semtypes : 'x', + $semester_id, + $user_id ?? $GLOBALS['user']->id, + ] + )); } /** @@ -328,7 +325,7 @@ class MvvOverlappingSelection extends SimpleORMap * * @return SimpleORMapCollection The excluded (hidden) conflicts. */ - public function getExcludedConflicts() + public function getExcludedConflicts(): SimpleORMapCollection { return $this->conflicts->findBy( 'comp_course_id', diff --git a/lib/models/StgteilVersion.php b/lib/models/StgteilVersion.php index 113875c260533e83af0367942a571a5efd6ed15b..40bdb8ad047468fab43178afdbb362f9bf2298ee 100644 --- a/lib/models/StgteilVersion.php +++ b/lib/models/StgteilVersion.php @@ -306,7 +306,7 @@ class StgteilVersion extends ModuleManagementModelTreeItem $replacements = [ $this->fassung_nr, $this->fassung_nr . ModuleManagementModel::getLocaleOrdinalNumberSuffix($this->fassung_nr), - $this->fassung_typ ? $GLOBALS['MVV_STGTEILVERSION']['FASSUNG_TYP'][$this->fassung_typ]['name'] : '', + $GLOBALS['MVV_STGTEILVERSION']['FASSUNG_TYP'][$this->fassung_typ]['name'] ?? '', $this->studiengangteil->fach->name, $this->getDisplaySemesterValidity(), trim($this->studiengangteil->kp), diff --git a/resources/assets/javascripts/lib/overlapping.js b/resources/assets/javascripts/lib/overlapping.js index 56896fee0d1121630263f9439089ca3aed1b6c16..05b1a223ac98254eabe85c659c5ca958373e9558 100644 --- a/resources/assets/javascripts/lib/overlapping.js +++ b/resources/assets/javascripts/lib/overlapping.js @@ -7,7 +7,8 @@ const Overlapping = { * @returns {undefined} */ init: function () { - $('#base-version-select').select2({ + let base_selection = $('#base-version-select'); + base_selection.select2({ placeholder: $gettext('Studiengangteil suchen'), minimumInputLength: 3, ajax: { @@ -31,7 +32,7 @@ const Overlapping = { $('#semtype-select').select2({ placeholder: $gettext('Veranstaltungstyp auswählen (optional)') }); - $('#base-version-select').on('select2:select', function () { + base_selection.on('select2:select', function () { $('#comp-versions-select').val(null).trigger('change'); $.ajax({ url: STUDIP.URLHelper.getURL('dispatch.php/admin/overlapping/comp_versions'), @@ -41,7 +42,7 @@ const Overlapping = { }, success: function(data) { if (data.results.length) { - var inputlength = 3; + let inputlength = 3; if (data.results.length < 4) { inputlength = 0; } @@ -55,33 +56,30 @@ const Overlapping = { } }); } else { - $('#comp-versions-select').select2({ + base_selection.select2({ placeholder: $gettext('Keine weitere Auswahl möglich') }); - $('#comp-versions-select').prop('disabled', true).trigger('change'); + base_selection.prop('disabled', true).trigger('change'); } } }); }); $('span.mvv-overlapping-exclude').on('click', function () { - const course_id = $(this).data('mvv-ovl-course'); - const selection_id = $(this).data('mvv-ovl-selection'); + const conflict_id = $(this).data('mvv-ovl-conflict'); $.ajax({ - method: 'post', - url: STUDIP.URLHelper.getURL('dispatch.php/admin/overlapping/set_exclude'), + method: 'get', + url: STUDIP.URLHelper.getURL('dispatch.php/admin/overlapping/exclude'), data: { - 'excluded': $(this).is('.mvv-overlapping-invisible') ? 1 : 0, - 'course_id': course_id, - 'selection_id': selection_id + 'conflict_id': conflict_id }, success() { $('.mvv-overlapping-exclude').each(function () { - if ($(this).data('mvv-ovl-course') == course_id) { + if ($(this).data('mvv-ovl-conflict') === conflict_id) { $(this).toggleClass('mvv-overlapping-invisible'); } + $(this).attr('title', $gettext('Veranstaltung berücksichtigen')); }); - $('.mvv-overlapping-exclude').attr('title', $gettext('Veranstaltung berücksichtigen')); $('.mvv-overlapping-invisible').attr('title', $gettext('Veranstaltung nicht berücksichtigen')); } diff --git a/resources/assets/stylesheets/scss/overlapping.scss b/resources/assets/stylesheets/scss/overlapping.scss index d8f6d528bda57497d3f0ef045413ac1a12eff418..6de5f48203afe6df0aecfdc6d491986ec16f0937 100644 --- a/resources/assets/stylesheets/scss/overlapping.scss +++ b/resources/assets/stylesheets/scss/overlapping.scss @@ -4,73 +4,69 @@ .mvv-ovl-base-abschnitt { position: relative; - width: 100%; - height: 30px; margin-bottom: 5px; - color: var(--dark-gray-color); font-weight: 700; - font-size: 16px; border-bottom: 1px solid var(--light-gray-color-40); h2 { - position: absolute; + position: relative; left: 5px; - border: none; - margin: 7px 0; + top: 7px; + padding-right: 150px; + margin-top: 0; + font-size: 1em; } & > div { position: absolute; - left: unset; right: 0; + bottom: 0; + white-space: nowrap; div { display: inline-block; - width: 25px; - margin-top: 5px; + width: 20px; + font-size: 1em; } } } ul.mvv-ovl-conflict { - width: 100%; - .mvv-ovl-base-modulteil, .mvv-ovl-comp-modulteil { + div.mvv-ovl-title { + margin-right: 150px; + display: inline-block; + /* margin-top: -21px; */ + } + + .mvv-ovl-modulteil { > div { position: absolute; top: 0; right: 0; text-align: right; - border-bottom: solid 1px var(--light-gray-color-40); + width: 150px; &:first-of-type { - left: 30px; + position: relative; + top: -21px; + left: 18px; width: auto; text-align: left; border-bottom: solid 1px var(--light-gray-color-40); + padding-right: 150px; } & > div { display: inline-block; - width: 25px; + width: 20px; text-align: left; } } - } - - .mvv-ovl-version { - font-size: 1.2em; - } -} -.mvv-ovl-base-course { - position: absolute; - width: 5px; - color: var(--red); - left: 10px; - - ~ label { - padding-left: 4px; + > ul { + top: -21px; + } } } @@ -85,3 +81,13 @@ ul.mvv-ovl-conflict { background: rgba(255, 255, 255, 0.5) url("#{$image-path}/icons/blue/visibility-invisible.svg") center center no-repeat; } } + +#admin-overlapping-index { + .select2-dropdown .select2-results .select2-results__option { + word-wrap: break-word; + white-space: normal; + } + .select2-selection__rendered .select2-selection__choice .select2-selection__content { + white-space: normal; + } +}