From d072920a676743dd4ba9d4cc4af080eee9809a01 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 12 Jul 2016 16:54:36 +0200 Subject: [exams] Show list of upcomfing exam lectures; allow adding exam period based on upcoming exam --- modules-available/exams/install.inc.php | 5 + modules-available/exams/lang/de/messages.json | 11 +- modules-available/exams/lang/de/module.json | 8 +- modules-available/exams/lang/de/template-tags.json | 18 ++- modules-available/exams/lang/en/messages.json | 1 + modules-available/exams/lang/en/template-tags.json | 12 +- modules-available/exams/page.inc.php | 173 +++++++++++++++------ .../exams/templates/page-add-edit-exam.html | 56 +++++-- .../exams/templates/page-exams-vis.html | 52 +++++++ modules-available/exams/templates/page-exams.html | 139 ++++++----------- .../exams/templates/page-main-heading.html | 3 + .../exams/templates/page-upcoming-lectures.html | 34 ++++ 12 files changed, 342 insertions(+), 170 deletions(-) create mode 100644 modules-available/exams/templates/page-exams-vis.html create mode 100644 modules-available/exams/templates/page-main-heading.html create mode 100644 modules-available/exams/templates/page-upcoming-lectures.html (limited to 'modules-available/exams') diff --git a/modules-available/exams/install.inc.php b/modules-available/exams/install.inc.php index 0cb1af8e..18be0be6 100644 --- a/modules-available/exams/install.inc.php +++ b/modules-available/exams/install.inc.php @@ -4,6 +4,7 @@ $res = array(); $res[] = tableCreate('exams', ' `examid` int(11) NOT NULL AUTO_INCREMENT, + `lectureid` char(36) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL `starttime` int(11) NOT NULL, `endtime` int(11) NOT NULL, `description` varchar(500) DEFAULT NULL, @@ -24,6 +25,10 @@ if (Database::exec("ALTER TABLE exams ADD INDEX `idx_daterange` ( `starttime` , $res[] = UPDATE_DONE; } +if (!tableHasColumn('exams', 'lectureid')) { + Database::exec("ALTER TABLE `exams` ADD `lectureid` CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NULL DEFAULT NULL AFTER `examid`"); +} + Database::exec("ALTER TABLE `exams` CHANGE `description` `description` varchar(500) DEFAULT NULL"); if (in_array(UPDATE_DONE, $res)) { diff --git a/modules-available/exams/lang/de/messages.json b/modules-available/exams/lang/de/messages.json index eda4257e..574cca93 100644 --- a/modules-available/exams/lang/de/messages.json +++ b/modules-available/exams/lang/de/messages.json @@ -3,10 +3,11 @@ "end-before-start": "Endzeitpunkt liegt vor Startzeitpunkt!", "endtime-invalid": "Ung\u00fcltige Endzeit: {{0}}", "error-while-saving-changes": "Fehler beim Speichern der \u00c4nderungen", - "exam-added-success": "Klausurzeitraum erfolgreich hinzugef\u00fcgt", - "exam-deleted-success": "Klausurzeitraum erfolgreich gel\u00f6scht", - "exam-not-added": "Klausurzeitraum konnte nicht hinzugef\u00fcgt werden", - "exam-not-deleted-error": "Klausurzeitraum konnte nicht gel\u00f6scht werden", - "invalid-exam-id": "Ung\u00fcltige Klausur-ID: {{0}}", + "exam-added-success": "Pr\u00fcfungszeitraum erfolgreich hinzugef\u00fcgt", + "exam-deleted-success": "Pr\u00fcfungszeitraum erfolgreich gel\u00f6scht", + "exam-not-added": "Pr\u00fcfungszeitraum konnte nicht hinzugef\u00fcgt werden", + "exam-not-deleted-error": "Pr\u00fcfungszeitraum konnte nicht gel\u00f6scht werden", + "invalid-exam-id": "Ung\u00fcltige Pr\u00fcfungs-ID: {{0}}", + "no-upcoming-lecture-exams": "Keine anstehenden Veranstaltungen, die als Pr\u00fcfung markiert sind", "starttime-invalid": "Ung\u00fcltige Startzeit: {{0}}" } \ No newline at end of file diff --git a/modules-available/exams/lang/de/module.json b/modules-available/exams/lang/de/module.json index 226810fd..4d9fd954 100644 --- a/modules-available/exams/lang/de/module.json +++ b/modules-available/exams/lang/de/module.json @@ -1,6 +1,6 @@ { - "module_name": "Klausurmodus", - "title_add-exam": "Klausur hinzuf\u00fcgen", - "title_edit-exam": "Klausur bearbeiten", - "warning_lecture_is_not_enabled": "Warnung: Diese Vorlesung ist nicht vom Dozenten aktiviert" + "module_name": "Pr\u00fcfungsmodus", + "title_add-exam": "Pr\u00fcfungszeitraum hinzuf\u00fcgen", + "title_edit-exam": "Pr\u00fcfungszeitraum bearbeiten", + "warning_lecture_is_not_enabled": "Warnung: Diese Veranstaltung ist nicht vom Dozenten aktiviert worden" } \ No newline at end of file diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json index 9b9225de..797cc371 100644 --- a/modules-available/exams/lang/de/template-tags.json +++ b/modules-available/exams/lang/de/template-tags.json @@ -1,17 +1,27 @@ { "lang_actions": "Aktionen", - "lang_addExam": "Klausurzeitraum hinzuf\u00fcgen", - "lang_allExamPeriods": "Alle Klausurzeitr\u00e4ume", + "lang_addExam": "Zeitraum hinzuf\u00fcgen", + "lang_addingBasedOnLecture": "F\u00fcge neuen Pr\u00fcfungszeitraum basierend auf vorhandener Veranstaltung an", + "lang_allExamPeriods": "Alle Pr\u00fcfungszeitr\u00e4ume", "lang_begin": "Beginn", "lang_begin_date": "Beginn Datum", "lang_begin_time": "Uhrzeit", "lang_deleteConfirmation": "Wirklich l\u00f6schen?", "lang_description": "Beschreibung", - "lang_editExam": "Klausurzeitraum bearbeiten", + "lang_duration": "Dauer", + "lang_editExam": "Zeitraum bearbeiten", "lang_end": "Ende", "lang_end_date": "Ende Datum", "lang_end_time": "Uhrzeit", + "lang_examModeDescription": "Hier k\u00f6nnen Sie bwLehrpool-R\u00e4ume zeitgesteuert in den Pr\u00fcfungsmodus versetzen. Im Pr\u00fcfungsmodus ist das Client-System st\u00e4rker abgeriegelt, sodass es sich zum Schreiben von E-Pr\u00fcfungen eignet. Nach dem Ein- bzw. Ausschalten des Pr\u00fcfungsmodus ist es notwendig, die Rechner in den betroffenen R\u00e4umen neuzustarten.", + "lang_global": "Global", + "lang_headingAllExamLectures": "Liste ausstehender Pr\u00fcfungsveranstaltungen", + "lang_headingGraphicalOverview": "Grafische Darstellung", + "lang_headingMain": "bwLehrpool Pr\u00fcfungsmodus", "lang_id": "ID", + "lang_lectureName": "Veranstaltungsname", "lang_location": "Raum\/Ort", - "lang_locations": "R\u00e4ume\/Orte" + "lang_locations": "R\u00e4ume\/Orte", + "lang_noDescription": "Keine Beschreibung", + "lang_timeFrame": "Zeitraum" } \ No newline at end of file diff --git a/modules-available/exams/lang/en/messages.json b/modules-available/exams/lang/en/messages.json index a62cc489..f07ffba1 100644 --- a/modules-available/exams/lang/en/messages.json +++ b/modules-available/exams/lang/en/messages.json @@ -8,5 +8,6 @@ "exam-not-added": "Exam period was not added", "exam-not-deleted-error": "Exam period was not deleted", "invalid-exam-id": "Invalid exam id: {{0}}", + "no-upcoming-lecture-exams": "No upcoming lectures which are marked as exam", "starttime-invalid": "Invalid start time: {{0}}" } \ No newline at end of file diff --git a/modules-available/exams/lang/en/template-tags.json b/modules-available/exams/lang/en/template-tags.json index 9c49c8a3..2e5abeff 100644 --- a/modules-available/exams/lang/en/template-tags.json +++ b/modules-available/exams/lang/en/template-tags.json @@ -1,17 +1,27 @@ { "lang_actions": "Actions", "lang_addExam": "Add exam period", + "lang_addingBasedOnLecture": "Adding exam period based on lecture", "lang_allExamPeriods": "All exam periods", "lang_begin": "Begin", "lang_begin_date": "Begin Date", "lang_begin_time": "Time", "lang_deleteConfirmation": "Are you sure?", "lang_description": "Description", + "lang_duration": "Duration", "lang_editExam": "Edit Exam Period", "lang_end": "End", "lang_end_date": "End Date", "lang_end_time": "Time", + "lang_examModeDescription": "Here you can define time spans during which selected rooms will be set to exam mode. In exam mode, the client computers are more locked down than usual so it is suitable for writing electronic exams.", + "lang_global": "Global", + "lang_headingAllExamLectures": "Upcoming lectures marked as exams", + "lang_headingGraphicalOverview": "Graphical overview", + "lang_headingMain": "bwLehrpool exam mode", "lang_id": "ID", + "lang_lectureName": "Lecture name", "lang_location": "Room\/Location", - "lang_locations": "Rooms\/Locations" + "lang_locations": "Rooms\/Locations", + "lang_noDescription": "No description", + "lang_timeFrame": "Time frame" } \ No newline at end of file diff --git a/modules-available/exams/page.inc.php b/modules-available/exams/page.inc.php index d0d2255f..d6b1ccea 100644 --- a/modules-available/exams/page.inc.php +++ b/modules-available/exams/page.inc.php @@ -12,12 +12,14 @@ class Page_Exams extends Page /** if examid is set, also add a column 'selected' **/ - protected function readLocations($examid = null) + protected function readLocations($examidOrLocations = null) { - if ($examid == null) { + if ($examidOrLocations == null) { $active = 0; + } elseif (is_array($examidOrLocations)) { + $active = $examidOrLocations; } else { - $tmp = Database::simpleQuery("SELECT locationid FROM exams_x_location WHERE examid= :examid", compact('examid')); + $tmp = Database::simpleQuery("SELECT locationid FROM exams_x_location WHERE examid= :examid", array('examid' => $examidOrLocations)); $active = array(); while ($row = $tmp->fetch(PDO::FETCH_ASSOC)) { $active[] = (int)$row['locationid']; @@ -29,7 +31,7 @@ class Page_Exams extends Page protected function readExams() { $tmp = Database::simpleQuery("SELECT examid, starttime, endtime, description, GROUP_CONCAT(locationid) AS locationids, " - . "GROUP_CONCAT(locationname) AS locationnames FROM exams " + . "GROUP_CONCAT(locationname SEPARATOR ', ') AS locationnames FROM exams " . "NATURAL LEFT JOIN exams_x_location NATURAL LEFT JOIN location GROUP BY examid"); while ($exam = $tmp->fetch(PDO::FETCH_ASSOC)) { $this->exams[] = $exam; @@ -39,20 +41,15 @@ class Page_Exams extends Page protected function readLectures() { $tmp = Database::simpleQuery( - "SELECT lectureid, locationid, displayname, starttime, endtime, isenabled " . + "SELECT lectureid, Group_Concat(locationid) as lids, displayname, starttime, endtime, isenabled, firstname, lastname, email " . "FROM sat.lecture " . + "INNER JOIN sat.user ON (user.userid = lecture.ownerid) " . "NATURAL LEFT JOIN sat.lecture_x_location " . - "WHERE isexam <> 0 AND starttime < :rangeMax AND endtime > :rangeMin", + "WHERE isexam <> 0 AND starttime < :rangeMax AND endtime > :rangeMin " . + "GROUP BY lectureid", ['rangeMax' => $this->rangeMax, 'rangeMin' => $this->rangeMin]); while ($lecture = $tmp->fetch(PDO::FETCH_ASSOC)) { - if (is_null($lecture['locationid'])) { - foreach ($this->locations as $location) { - $lecture['locationid'] = $location['locationid']; - $this->lectures[] = $lecture; - } - } else { - $this->lectures[] = $lecture; - } + $this->lectures[] = $lecture; } } @@ -81,8 +78,8 @@ class Page_Exams extends Page $locationids = explode(',', $e['locationids']); if ($locationids[0] == 0) { $locationids = []; - foreach($this->locations as $l) { - $locationids[] = $l['locationid']; + foreach($this->locations as $location) { + $locationids[] = $location['locationid']; } } foreach ($locationids as $locationid) { @@ -98,20 +95,28 @@ class Page_Exams extends Page } } /* add the lectures */ + $allLocationIds = array_map(function($loc) { return $loc['locationid']; }, $this->locations); $i = 2; - foreach ($this->lectures as $l) { - $mark = ''; - $out[] = [ - 'id' => $l['lectureid'] . '/' . $l['locationid'], - 'content' => htmlspecialchars($l['displayname']) . $mark, - 'title' => $l['isenabled'] ? '' : Dictionary::translate('warning_lecture_is_not_enabled'), - 'start' => intval($l['starttime']) * 1000, - 'end' => intval($l['endtime']) * 1000, - 'group' => $l['locationid'], - 'className' => $l['isenabled'] ? '' : 'disabled', - 'editable' => false, - 'subgroup' => $i++, - ]; + foreach ($this->lectures as $lecture) { + $mark = ''; + if (empty($lecture['lids'])) { + $locations = $allLocationIds; + } else { + $locations = explode(',', $lecture['lids']); + } + foreach ($locations as $location) { + $out[] = [ + 'id' => $lecture['lectureid'] . '/' . $location, + 'content' => htmlspecialchars($lecture['displayname']) . $mark, + 'title' => $lecture['isenabled'] ? '' : Dictionary::translate('warning_lecture_is_not_enabled'), + 'start' => intval($lecture['starttime']) * 1000, + 'end' => intval($lecture['endtime']) * 1000, + 'group' => $location, + 'className' => $lecture['isenabled'] ? '' : 'disabled', + 'editable' => false, + 'subgroup' => $i++, + ]; + } } return json_encode($out); @@ -136,7 +141,7 @@ class Page_Exams extends Page $now = time(); foreach ($this->exams as $exam) { if ($exam['endtime'] < $now) { - $exam['rowClass'] = 'gray'; + $exam['rowClass'] = 'text-muted'; $exam['btnClass'] = 'btn-success'; $exam['liesInPast'] = true; } else { @@ -149,6 +154,42 @@ class Page_Exams extends Page return $out; } + protected function makeLectureExamList() + { + $out = []; + $now = time(); + $cutoff = strtotime('+ 5 day'); + foreach ($this->lectures as $lecture) { + if ($lecture['endtime'] < $now || $lecture['starttime'] > $cutoff) + continue; + $entry = $lecture; + if (!$lecture['isenabled']) { + $entry['class'] = 'text-muted'; + } + $entry['starttime_s'] = date('Y-m-d H:i', $lecture['starttime']); + $entry['endtime_s'] = date('Y-m-d H:i', $lecture['endtime']); + $duration = $lecture['endtime'] - $lecture['starttime']; + if ($duration < 86400) { + $entry['duration_s'] = gmdate('H:i', $duration); + } + $out[] = $entry; + } + return $out; + } + + protected function makeEditFromArray($source) + { + if (!isset($source['description']) && isset($source['displayname'])) { + $source['description'] = $source['displayname']; + } + return [ + 'starttime_date' => date('Y-m-d', $source['starttime']), + 'starttime_time' => date('H:i', $source['starttime']), + 'endtime_date' => date('Y-m-d', $source['endtime']), + 'endtime_time' => date('H:i', $source['endtime']) + ] + $source; + } + private function isDateSane($time) { return ($time >= $this->rangeMin && $time <= $this->rangeMax); @@ -249,6 +290,10 @@ class Page_Exams extends Page $this->readLocations(); $this->readLectures(); + } elseif ($this->action === 'add') { + + $this->readLectures(); + } elseif ($this->action === 'edit') { $examid = Request::get('examid', 0, 'int'); @@ -258,6 +303,7 @@ class Page_Exams extends Page Util::redirect('?do=exams'); } $this->readLocations($examid); + $this->readLectures(); } elseif ($this->action === 'save') { @@ -288,32 +334,59 @@ class Page_Exams extends Page protected function doRender() { if ($this->action === "show") { - Render::setTitle("All Exams"); - Render::addTemplate('page-exams', - ['exams' => $this->makeExamsForTemplate(), - 'exams_json' => $this->makeItemsForVis(), - 'rooms_json' => $this->makeGroupsForVis(), - 'vis_begin' => strtotime('-5 minute') * 1000, - 'vis_end' => strtotime('+2 day') * 1000, - 'vis_min_date' => $this->rangeMin * 1000, - 'vis_max_date' => $this->rangeMax * 1000, - 'axis_label' => (count($this->locations) > 5 ? 'both' : 'bottom') + + // General title and description + Render::addTemplate('page-main-heading'); + // List of defined exam periods + Render::addTemplate('page-exams', [ + 'exams' => $this->makeExamsForTemplate() + ]); + // List of upcoming lectures marked as exam + $upcoming = $this->makeLectureExamList(); + if (empty($upcoming)) { + Message::addInfo('no-upcoming-lecture-exams'); + } else { + Render::addTemplate('page-upcoming-lectures', [ + 'pending_lectures' => $upcoming ]); + } + // Vis.js timeline + Render::addTemplate('page-exams-vis', [ + 'exams_json' => $this->makeItemsForVis(), + 'rooms_json' => $this->makeGroupsForVis(), + 'vis_begin' => strtotime('-5 minute') * 1000, + 'vis_end' => strtotime('+2 day') * 1000, + 'vis_min_date' => $this->rangeMin * 1000, + 'vis_max_date' => $this->rangeMax * 1000, + 'axis_label' => (count($this->locations) > 5 ? 'both' : 'bottom'), + 'utc_offset' => date('P') + ]); + } elseif ($this->action === "add") { + Render::setTitle(Dictionary::translate('title_add-exam')); - $this->readLocations(); - Render::addTemplate('page-add-edit-exam', ['locations' => $this->locations]); + $data = []; + $baseLecture = Request::any('lectureid', false, 'string'); + $locations = null; + if ($baseLecture !== false) { + foreach ($this->lectures as $lecture) { + if ($lecture['lectureid'] === $baseLecture) { + $data['exam'] = $this->makeEditFromArray($lecture); + $locations = explode(',', $lecture['lids']); + break; + } + } + } + $this->readLocations($locations); + $data['locations'] = $this->locations; + Render::addTemplate('page-add-edit-exam', $data); + } elseif ($this->action === 'edit') { + Render::setTitle(Dictionary::translate('title_edit-exam')); - $exam = [ - 'examid' => $this->currentExam['examid'], - 'starttime_date' => date('Y-m-d', $this->currentExam['starttime']), - 'starttime_time' => date('H:i', $this->currentExam['starttime']), - 'endtime_date' => date('Y-m-d', $this->currentExam['endtime']), - 'endtime_time' => date('H:i', $this->currentExam['endtime']), - 'description' => $this->currentExam['description'] - ]; + $exam = $this->makeEditFromArray($this->currentExam); Render::addTemplate('page-add-edit-exam', ['exam' => $exam, 'locations' => $this->locations]); + } } diff --git a/modules-available/exams/templates/page-add-edit-exam.html b/modules-available/exams/templates/page-add-edit-exam.html index d112095f..3f0ef372 100644 --- a/modules-available/exams/templates/page-add-edit-exam.html +++ b/modules-available/exams/templates/page-add-edit-exam.html @@ -4,6 +4,9 @@ {{^exam.examid}}

{{lang_addExam}}

{{/exam.examid}} +{{#exam.displayname}} +
{{lang_addingBasedOnLecture}}:
{{exam.displayname}}
+{{/exam.displayname}}
@@ -64,6 +67,13 @@
+ +
+
+ {{lang_duration}}: - +
+
+
@@ -80,6 +90,8 @@ diff --git a/modules-available/exams/templates/page-exams-vis.html b/modules-available/exams/templates/page-exams-vis.html new file mode 100644 index 00000000..e347900b --- /dev/null +++ b/modules-available/exams/templates/page-exams-vis.html @@ -0,0 +1,52 @@ +

{{lang_headingGraphicalOverview}}

+ +
+ + diff --git a/modules-available/exams/templates/page-exams.html b/modules-available/exams/templates/page-exams.html index 5fa4c0c5..184a69e3 100644 --- a/modules-available/exams/templates/page-exams.html +++ b/modules-available/exams/templates/page-exams.html @@ -1,96 +1,47 @@ -
-

{{lang_allExamPeriods}}

- -
- - - - - - - - - {{#exams}} - - - - - - - - {{/exams}} - -
{{lang_id}}{{lang_locations}}{{lang_begin}}{{lang_end}}{{lang_actions}}
{{examid}} - {{locationnames}} -
{{description}}
-
{{starttime_s}}{{endtime_s}} - - {{^liesInPast}} - - {{/liesInPast}} - {{lang_edit}} - - - - -
- -
- - -
-
-
+

{{lang_allExamPeriods}}

+ +
+ + + + + + + + + {{#exams}} + + + + + + + + {{/exams}} +
{{lang_id}}{{lang_locations}}{{lang_begin}}{{lang_end}}{{lang_actions}}
{{examid}} + {{locationnames}} + {{^locationnames}} + {{lang_global}} + {{/locationnames}} +
+ {{description}} + {{^description}} + {{lang_noDescription}} + {{/description}} +
+
{{starttime_s}}{{endtime_s}} +
+ {{^liesInPast}} + + {{/liesInPast}} + {{lang_edit}} + + + +
+
- + diff --git a/modules-available/exams/templates/page-main-heading.html b/modules-available/exams/templates/page-main-heading.html new file mode 100644 index 00000000..87b92a20 --- /dev/null +++ b/modules-available/exams/templates/page-main-heading.html @@ -0,0 +1,3 @@ +

{{lang_headingMain}}

+ +

{{lang_examModeDescription}}

\ No newline at end of file diff --git a/modules-available/exams/templates/page-upcoming-lectures.html b/modules-available/exams/templates/page-upcoming-lectures.html new file mode 100644 index 00000000..4a62bc29 --- /dev/null +++ b/modules-available/exams/templates/page-upcoming-lectures.html @@ -0,0 +1,34 @@ +

{{lang_headingAllExamLectures}}

+ +
+ + + + + + + {{#pending_lectures}} + + + + + + {{/pending_lectures}} +
{{lang_lectureName}}{{lang_timeFrame}}{{lang_actions}}
+ {{displayname}} + + + {{starttime_s}}   {{endtime_s}} +
{{duration_s}}
+
+ +
+
\ No newline at end of file -- cgit v1.2.3-55-g7522