aboutsummaryrefslogtreecommitdiff
path: root/app/controllers/admin/overlapping.php
diff options
context:
space:
mode:
Diffstat (limited to 'app/controllers/admin/overlapping.php')
-rw-r--r--app/controllers/admin/overlapping.php462
1 files changed, 331 insertions, 131 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
+ ]
+ );
}
}