aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Thienel <thienel@data-quest.de>2024-12-18 11:13:59 +0000
committerPeter Thienel <thienel@data-quest.de>2024-12-18 11:13:59 +0000
commit061f8168db6de9c1dded98c176d756cbbf24cb57 (patch)
tree1f65dad592a6d0720f21f9d5676c5d6be26b9e5a
parent6c14b24a0ff71f26bea19c827860220371e5eccf (diff)
Resolve "Stundenplanansicht für Überschneidungsfreiheitschecks"
Closes #3323 Merge request studip/studip!2337
-rw-r--r--app/controllers/admin/overlapping.php462
-rw-r--r--app/controllers/search/studiengaenge.php4
-rw-r--r--app/views/admin/overlapping/admin_info.php1
-rw-r--r--app/views/admin/overlapping/buttons.php46
-rw-r--r--app/views/admin/overlapping/conflicts.php173
-rw-r--r--app/views/admin/overlapping/course_conflict.php2
-rw-r--r--app/views/admin/overlapping/courses.php31
-rw-r--r--app/views/admin/overlapping/index.php5
-rw-r--r--app/views/admin/overlapping/info_dialog.php7
-rw-r--r--app/views/admin/overlapping/modul.php7
-rw-r--r--app/views/admin/overlapping/overlapping.php12
-rw-r--r--app/views/admin/overlapping/planer.php27
-rw-r--r--app/views/admin/overlapping/selection.php56
-rw-r--r--app/views/search/studiengaenge/verlauf.php9
-rw-r--r--lib/models/MvvOverlappingSelection.php141
-rw-r--r--lib/models/StgteilVersion.php2
-rw-r--r--resources/assets/javascripts/lib/overlapping.js26
-rw-r--r--resources/assets/stylesheets/scss/overlapping.scss64
18 files changed, 688 insertions, 387 deletions
diff --git a/app/controllers/admin/overlapping.php b/app/controllers/admin/overlapping.php
index 8072ff5..101292b 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 e17eb2a..0dd5c7d 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 1eeac77..7dbd179 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 0000000..64c3082
--- /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 6d22c1d..f1cd914 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 0000000..345d4ba
--- /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 aa2e83f..5cb0a7d 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 892a198..7736186 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 0000000..63f6c25
--- /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 fe9d483..aaf31d2 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 328122a..5d5c966 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 0000000..bf71e73
--- /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'] ?>">
+ &nbsp;
+ </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 18b1aef..11c6208 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 5451b43..4464e66 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 525076c..89a765c 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 113875c..40bdb8a 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 56896fe..05b1a22 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 d8f6d52..6de5f48 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;
+ }
+}