summaryrefslogtreecommitdiffstats
path: root/modules-available/exams
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/exams')
-rw-r--r--modules-available/exams/baseconfig/getconfig.inc.php20
-rw-r--r--modules-available/exams/inc/exams.inc.php9
-rw-r--r--modules-available/exams/lang/de/template-tags.json12
-rw-r--r--modules-available/exams/lang/en/template-tags.json12
-rw-r--r--modules-available/exams/page.inc.php72
-rw-r--r--modules-available/exams/templates/page-add-edit-exam.html223
-rw-r--r--modules-available/exams/templates/page-exams.html12
7 files changed, 259 insertions, 101 deletions
diff --git a/modules-available/exams/baseconfig/getconfig.inc.php b/modules-available/exams/baseconfig/getconfig.inc.php
index 10aa1d84..7e4a70df 100644
--- a/modules-available/exams/baseconfig/getconfig.inc.php
+++ b/modules-available/exams/baseconfig/getconfig.inc.php
@@ -1,28 +1,32 @@
<?php
-$foofoo = function($machineUuid) {
+/** @var ?string $uuid */
+/** @var ?string $ip */
+
+if ($uuid !== null) {
// Leave clients in any runmode alone
$res = Database::queryFirst('SELECT machineuuid FROM runmode WHERE machineuuid = :uuid',
- array('uuid' => $machineUuid), true);
+ array('uuid' => $uuid), true);
if (is_array($res))
return;
// Check if exam mode should apply
$locations = ConfigHolder::get('SLX_LOCATIONS');
- if ($locations === false) {
+ if ($locations === null) {
$locationIds = [];
} else {
$locationIds = explode(' ', $locations);
}
if (Exams::isInExamMode($locationIds, $lectureId, $autoLogin)) {
ConfigHolder::add('SLX_EXAM', 'yes', 10000);
- if (strlen($lectureId) > 0) {
+ if (!empty($lectureId)) {
ConfigHolder::add('SLX_EXAM_START', $lectureId, 10000);
}
- if (strlen($autoLogin) > 0) {
+ if (!empty($autoLogin)) {
ConfigHolder::add('SLX_AUTOLOGIN', $autoLogin, 10000);
}
ConfigHolder::add('SLX_SYSTEMD_TARGET', 'exam-mode', 10000);
+ ConfigHolder::add('SLX_RUNMODE_MODULE', 'exams', 10000);
+ // No saver
+ ConfigHolder::add('SLX_SCREEN_SAVER_TIMEOUT', '0', 1000);
}
-};
-
-$foofoo($uuid); \ No newline at end of file
+} \ No newline at end of file
diff --git a/modules-available/exams/inc/exams.inc.php b/modules-available/exams/inc/exams.inc.php
index da4dec85..2a54c262 100644
--- a/modules-available/exams/inc/exams.inc.php
+++ b/modules-available/exams/inc/exams.inc.php
@@ -4,14 +4,11 @@ class Exams
{
/**
- * @param int[] of location ids. must bot be an associative array.
- * @return: bool true iff for any of the given location ids an exam is scheduled.
+ * @param int[] $locationIds of location ids. must be an associative array.
+ * @return bool true iff for any of the given location ids an exam is scheduled.
**/
- public static function isInExamMode($locationIds, &$lectureId = false, &$autoLogin = false)
+ public static function isInExamMode(array $locationIds, ?string &$lectureId = null, ?string &$autoLogin = null): bool
{
- if (!is_array($locationIds)) {
- $locationIds = array($locationIds);
- }
if (empty($locationIds)) {
$locationIds[] = 0;
}
diff --git a/modules-available/exams/lang/de/template-tags.json b/modules-available/exams/lang/de/template-tags.json
index 0fbaf0a1..d52a3199 100644
--- a/modules-available/exams/lang/de/template-tags.json
+++ b/modules-available/exams/lang/de/template-tags.json
@@ -10,6 +10,7 @@
"lang_begin": "Beginn",
"lang_begin_date": "Beginn Datum",
"lang_begin_time": "Uhrzeit",
+ "lang_checkLocationSelectionHint": "Stellen Sie sicher, dass die gew\u00fcnschte(n) Pr\u00fcfungsveranstaltung(en) in ihrer Raumbeschr\u00e4nkung mit den hier ausgew\u00e4hlten R\u00e4umen \u00fcbereinstimmen.",
"lang_comfirmGlobalExam": "Wollen Sie wirklich eine globale Pr\u00fcfung definieren? Im gew\u00e4hlten Zeitraum werden s\u00e4mtliche R\u00e4ume in den Pr\u00fcfungsmodus geschaltet.",
"lang_dateTime": "Datum\/Uhrzeit",
"lang_deleteConfirmation": "Wirklich l\u00f6schen?",
@@ -18,7 +19,12 @@
"lang_end": "Ende",
"lang_end_date": "Ende Datum",
"lang_end_time": "Uhrzeit",
+ "lang_examEndAfterLectureEnd": "Der gew\u00e4hlte Klausurzeitraum endet sp\u00e4ter, als die gew\u00e4hlte Veranstaltung endet.",
+ "lang_examEndBeforeLectureEnd": "Der gew\u00e4hlte Klausurzeitraum endet fr\u00fcher, als die gew\u00e4hlte Veranstaltung endet.",
"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_examStartAfterLectureEnd": "Der gew\u00e4hlte Klausurzeitraum beginnt, nachdem die gew\u00e4hlte Veranstaltung endet, oder kurz vor derem Ende.",
+ "lang_examStartAfterLectureStart": "Der gew\u00e4hlte Klausurzeitraum beginnt sp\u00e4ter, als die gew\u00e4hlte Veranstaltung startet.",
+ "lang_examStartBeforeLectureStart": "Der gew\u00e4hlte Klausurzeitraum beginnt, bevor die gew\u00e4hlte Veranstaltung startet. Ein Autostart der Veranstaltung wird fehlschlagen.",
"lang_global": "Global",
"lang_headingAddExam": "Zeitraum hinzuf\u00fcgen",
"lang_headingAllExamLectures": "Ausstehende Pr\u00fcfungsveranstaltungen (30 Tage)",
@@ -27,12 +33,16 @@
"lang_headingMain": "bwLehrpool Pr\u00fcfungsmodus",
"lang_id": "ID",
"lang_lectureName": "Veranstaltungsname",
- "lang_lectureOutOfRange": "Achtung: Der oben angegebene Zeitraum ist k\u00fcrzer als die Dauer der Veranstaltung",
+ "lang_lectureNotForLocation": "Diese Veranstaltung findet nicht im oben ausgew\u00e4hlten Raum statt",
+ "lang_lectureTimespan": "Dauer der Veranstaltung",
"lang_location": "Raum\/Ort",
"lang_locationInfo": "W\u00e4hlen Sie hier die R\u00e4ume und Orte aus, die w\u00e4hrend des unten ausgew\u00e4hlten Zeitraums in den Pr\u00fcfungsmodus versetzt werden. Wenn sie hier keine Auswahl treffen, werden alle R\u00e4ume in den Pr\u00fcfungsmodus versetzt.",
"lang_locations": "R\u00e4ume\/Orte",
"lang_moreThanOneDay": "Mehr als ein Tag",
"lang_noDescription": "Keine Beschreibung",
"lang_none": "(Keine)",
+ "lang_sanityCheck": "Plausibilit\u00e4tspr\u00fcfung",
+ "lang_startAfterEnd": "Ende liegt vor Start",
+ "lang_startOrEndInvalid": "Start- oder Endzeitpunkt ung\u00fcltig",
"lang_timeFrame": "Zeitraum"
} \ 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 52173740..3359a28a 100644
--- a/modules-available/exams/lang/en/template-tags.json
+++ b/modules-available/exams/lang/en/template-tags.json
@@ -10,6 +10,7 @@
"lang_begin": "Begin",
"lang_begin_date": "Begin Date",
"lang_begin_time": "Time",
+ "lang_checkLocationSelectionHint": "Make sure that the according lecture(s) will have their location restrictions set accordingly.",
"lang_comfirmGlobalExam": "Do you really want to create a global exam? Every single room will be set to lecture mode during the selected time period.",
"lang_dateTime": "Date\/Time",
"lang_deleteConfirmation": "Are you sure?",
@@ -18,7 +19,12 @@
"lang_end": "End",
"lang_end_date": "End Date",
"lang_end_time": "Time",
+ "lang_examEndAfterLectureEnd": "Specified exam interval ends after selected lecture ends.",
+ "lang_examEndBeforeLectureEnd": "Specified exam interval ends before selected lecture ends.",
"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_examStartAfterLectureEnd": "Specified exam interval starts after selected lecture ends (or shortly before it ends).",
+ "lang_examStartAfterLectureStart": "Specified exam interval starts after selected lecture starts.",
+ "lang_examStartBeforeLectureStart": "Specified exam interval starts before selected lecture starts. (Auto-)starting the lecture before it's valid will fail.",
"lang_global": "Global",
"lang_headingAddExam": "Add Exam Period",
"lang_headingAllExamLectures": "Upcoming Lectures Marked As Exams (30 Days)",
@@ -27,12 +33,16 @@
"lang_headingMain": "bwLehrpool Exam Mode",
"lang_id": "ID",
"lang_lectureName": "Lecture name",
- "lang_lectureOutOfRange": "Hint: The exam period given above is shorter than the duration of the given lecture",
+ "lang_lectureNotForLocation": "This lecture is not visible at the location selected above",
+ "lang_lectureTimespan": "Lecture time span",
"lang_location": "Room\/Location",
"lang_locationInfo": "Select the rooms and locations you want to enable the exam mode in. Selecting nothing at all means that all clients will boot into exam mode during the given time period.",
"lang_locations": "Rooms\/Locations",
"lang_moreThanOneDay": "More than one day",
"lang_noDescription": "No description",
"lang_none": "(None)",
+ "lang_sanityCheck": "Sanity check",
+ "lang_startAfterEnd": "Start lies after end",
+ "lang_startOrEndInvalid": "Start oder end is invalid",
"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 23a5bc39..d229b883 100644
--- a/modules-available/exams/page.inc.php
+++ b/modules-available/exams/page.inc.php
@@ -23,7 +23,7 @@ class Page_Exams extends Page
} else {
$tmp = Database::simpleQuery("SELECT locationid FROM exams_x_location WHERE examid= :examid", array('examid' => $examidOrLocations));
$active = array();
- while ($row = $tmp->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($tmp as $row) {
$active[] = (int)$row['locationid'];
}
}
@@ -40,7 +40,7 @@ class Page_Exams extends Page
. "GROUP BY examid "
. "ORDER BY examid ASC");
- while ($exam = $tmp->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($tmp as $exam) {
$view = $edit = false;
// User has permission for all locations
if (in_array(0, $this->userViewLocations)) {
@@ -79,7 +79,7 @@ class Page_Exams extends Page
protected function readLectures()
{
$tmp = Database::simpleQuery(
- "SELECT lectureid, Group_Concat(locationid) as lids, displayname, starttime, endtime, isenabled, firstname, lastname, email " .
+ "SELECT lectureid, Group_Concat(locationid) as lids, islocationprivate, 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 " .
@@ -87,7 +87,7 @@ class Page_Exams extends Page
"GROUP BY lectureid " .
"ORDER BY starttime ASC, displayname ASC",
['rangeMax' => $this->rangeMax, 'rangeMin' => $this->rangeMin]);
- while ($lecture = $tmp->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($tmp as $lecture) {
$this->lectures[] = $lecture;
}
}
@@ -103,7 +103,7 @@ class Page_Exams extends Page
}
// returns true if user is allowed to edit the exam
- protected function userCanEditExam($examid = NULL)
+ protected function userCanEditExam(string $examid = NULL): bool
{
if (in_array(0, $this->userEditLocations)) // Trivial case -- don't query if global perms
return true;
@@ -111,16 +111,19 @@ class Page_Exams extends Page
return User::hasPermission('exams.edit');
// Check locations of existing exam
$res = Database::simpleQuery("SELECT locationid FROM exams_x_location WHERE examid= :examid", array('examid' => $examid));
- while ($locId = $res->fetch(PDO::FETCH_ASSOC)) {
+ foreach ($res as $locId) {
if (!in_array($locId['locationid'], $this->userEditLocations))
return false;
}
return true;
}
- // checks if user is allowed to save an exam with all the locations
- // needs information if it's add (second para = true) or edit (second para = false)
- protected function userCanEditLocation($locationids) {
+ /**
+ * checks if user is allowed to save an exam with all the locations
+ * needs information if it's add (second para = true) or edit (second para = false)
+ */
+ protected function userCanEditLocation(array $locationids): bool
+ {
return empty(array_diff($locationids, $this->userEditLocations));
}
@@ -213,10 +216,16 @@ class Page_Exams extends Page
return json_encode($out);
}
- protected function makeExamsForTemplate()
+ /**
+ * @return array{exams: array, decollapse: bool}
+ */
+ protected function makeExamsForTemplate(): array
{
$out = [];
$now = time();
+ $cutoff = strtotime('-90 days');
+ $foundActive = false;
+ $hasCollapsed = false;
if (is_array($this->exams)) {
foreach ($this->exams as $exam) {
if ($exam['endtime'] < $now) {
@@ -225,16 +234,27 @@ class Page_Exams extends Page
$exam['liesInPast'] = true;
} else {
$exam['btnClass'] = 'btn-danger';
+ if ($exam['starttime'] < $now) {
+ $exam['rowClass'] = 'slx-bold';
+ }
+ }
+ if (!$foundActive) {
+ if ($exam['endtime'] > $cutoff) {
+ $foundActive = true;
+ } else {
+ $exam['rowClass'] .= ' collapse';
+ $hasCollapsed = true;
+ }
}
$exam['starttime_s'] = date('Y-m-d H:i', $exam['starttime']);
$exam['endtime_s'] = date('Y-m-d H:i', $exam['endtime']);
$out[] = $exam;
}
}
- return $out;
+ return ['exams' => $out, 'decollapse' => $hasCollapsed];
}
- protected function makeLectureExamList()
+ protected function makeLectureExamList(): array
{
$out = [];
$now = time();
@@ -274,15 +294,15 @@ class Page_Exams extends Page
] + $source;
}
- private function isDateSane($time)
+ private function isDateSane(int $time): bool
{
- return ($time >= $this->rangeMin && $time <= $this->rangeMax);
+ return ($time >= strtotime('-10 years') && $time <= strtotime('+10 years'));
}
private function saveExam()
{
if (!Request::isPost()) {
- Util::traceError('Is not post');
+ ErrorHandler::traceError('Is not post');
}
/* process form-data */
$locationids = Request::post('locations', [], "ARRAY");
@@ -439,11 +459,17 @@ class Page_Exams extends Page
} elseif ($this->action === false) {
- Util::traceError("action not implemented");
+ ErrorHandler::traceError("action not implemented");
}
}
+ private function getLocationLookupJson()
+ {
+ $locs = Location::getLocationsAssoc(); // Add key x so we get an object, not array
+ return json_encode(['x' => 0] + array_map(function ($item) { return $item['children']; }, $locs));
+ }
+
protected function doRender()
{
if ($this->action === "show") {
@@ -452,7 +478,7 @@ class Page_Exams extends Page
// General title and description
Render::addTemplate('page-main-heading');
// List of defined exam periods
- $params = ['exams' => $this->makeExamsForTemplate()];
+ $params = $this->makeExamsForTemplate();
Permission::addGlobalTags($params['perms'], NULL, ['exams.edit']);
Render::addTemplate('page-exams', $params);
// List of upcoming lectures marked as exam
@@ -481,7 +507,7 @@ class Page_Exams extends Page
} elseif ($this->action === "add") {
Render::setTitle(Dictionary::translate('title_add-exam'));
- $data = [];
+ $data = ['locmap' => $this->getLocationLookupJson()];
$baseLecture = Request::any('lectureid', false, 'string');
$locations = null;
if ($baseLecture !== false) {
@@ -519,10 +545,12 @@ class Page_Exams extends Page
}
}
- $data = [];
- $data['exam'] = $exam;
- $data['locations'] = $this->locations;
- $data['lectures'] = $this->lectures;
+ $data = [
+ 'locmap' => $this->getLocationLookupJson(),
+ 'exam' => $exam,
+ 'locations' => $this->locations,
+ 'lectures' => $this->lectures,
+ ];
// if user has no permission to edit for this location, disable the location in the select
foreach ($data['locations'] as &$loc) {
diff --git a/modules-available/exams/templates/page-add-edit-exam.html b/modules-available/exams/templates/page-add-edit-exam.html
index a45cbac2..43ac46dc 100644
--- a/modules-available/exams/templates/page-add-edit-exam.html
+++ b/modules-available/exams/templates/page-add-edit-exam.html
@@ -10,6 +10,8 @@
<form class="form" method="POST" action="?do=exams" id="tolleform">
+ <!-- fake button to prevent return from messing things up -->
+ <button type="submit" hidden onclick="return false"></button>
<div class="panel panel-default">
<div class="panel-heading"><label for="locations">{{lang_location}}</label></div>
<div class="panel-body">
@@ -23,6 +25,7 @@
{{/locations}}
</select>
</div>
+ {{lang_checkLocationSelectionHint}}
</div>
</div>
@@ -77,10 +80,10 @@
</div>
</div>
- <div class="panel">
- <div class="panel-body">
- {{lang_duration}}: <span id="exam-duration">-</span>
- </div>
+ <div>
+ {{lang_duration}}: <span id="exam-duration">-</span>
+ <span class="hidden" id="txt-invalid">{{lang_startOrEndInvalid}}</span>
+ <span class="hidden" id="txt-reverse">{{lang_startAfterEnd}}</span>
</div>
</div>
</div>
@@ -88,29 +91,43 @@
<div class="panel panel-default">
<div class="panel-heading"><label for="lecturelist">{{lang_autoStartLecture}}</label></div>
<div class="panel-body">
- <div class="row form-group">
- <div class="form-group col-xs-12">
- <p><i>{{lang_autoStartInfo}}</i></p>
- <div class="input-group">
- <span class="input-group-addon">
- <span class="glyphicon glyphicon-pencil"></span>
- </span>
- <select class="form-control" id="lecturelist" name="lectureid">
- <option value="">{{lang_none}}</option>
- {{#lectures}}
- <option data-from="{{starttime}}" data-to="{{endtime}}" value="{{lectureid}}" {{selected}} >{{displayname}}</option>
- {{/lectures}}
- </select>
- </div>
+ <div class="form-group">
+ <p><i>{{lang_autoStartInfo}}</i></p>
+ <div class="input-group">
+ <span class="input-group-addon">
+ <span class="glyphicon glyphicon-pencil"></span>
+ </span>
+ <select class="form-control" id="lecturelist" name="lectureid">
+ <option value="">{{lang_none}}</option>
+ {{#lectures}}
+ <option data-from="{{starttime}}" data-to="{{endtime}}"
+ {{#islocationprivate}}data-locations="{{lids}}"{{/islocationprivate}}
+ value="{{lectureid}}" {{selected}} >{{displayname}}</option>
+ {{/lectures}}
+ </select>
</div>
- <div class="form-group col-xs-12">
- <div class="checkbox"><input id="autologin" type="checkbox" name="autologin" value="demo" class="form-control" {{#exam.autologin}}checked{{/exam.autologin}}><label for="autologin">{{lang_autoLogin}}</label></div>
- <p><i>{{lang_autoLoginInfo}}</i></p>
+ <b id="sanity-check" class="slx-smallspace collapse">{{lang_sanityCheck}}</b>
+ <div id="warn-range" class="collapse">
+ <div class="text-warning">
+ <div class="item start-before-lecture-start text-danger">{{lang_examStartBeforeLectureStart}}</div>
+ <div class="item start-after-lecture-start">{{lang_examStartAfterLectureStart}}</div>
+ <div class="item start-after-lecture-end text-danger">{{lang_examStartAfterLectureEnd}}</div>
+ <div class="item end-before-lecture-end">{{lang_examEndBeforeLectureEnd}}</div>
+ <div class="item end-after-lecture-end">{{lang_examEndAfterLectureEnd}}</div>
+ </div>
+ {{lang_lectureTimespan}}:
+ <span class="lecture-range"></span>
</div>
- <div class="col-xs-12" id="lecture-info">
- -
+ <div id="warn-locations" class="text-danger collapse">
+ {{lang_lectureNotForLocation}}:
+ <span class="locname"></span>
</div>
</div>
+ <div class="slx-space"></div>
+ <div class="form-group">
+ <div class="checkbox"><input id="autologin" type="checkbox" name="autologin" value="demo" class="form-control" {{#exam.autologin}}checked{{/exam.autologin}}><label for="autologin">{{lang_autoLogin}}</label></div>
+ <p><i>{{lang_autoLoginInfo}}</i></p>
+ </div>
</div>
</div>
@@ -126,7 +143,7 @@
<input type="hidden" name="examid" value="{{exam.examid}}">
<div class="text-right" style="margin-bottom: 20px">
<button type="button" id="cancelButton" class="btn btn-default" style="margin-right: 10px">{{lang_cancel}}</button>
- <button type="button" onclick="checkGlobalExam()" id="saveButton" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
+ <button type="button" id="saveButton" class="btn btn-primary"><span class="glyphicon glyphicon-floppy-disk"></span> {{lang_save}}</button>
</div>
<div class ="modal fade" id="confirmGlobalModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
@@ -149,18 +166,11 @@
<script type="application/javascript"><!--
-function checkGlobalExam() {
- if ($('#locations option:selected').length === 0 && $('#locations option').length > 1) {
- $("#confirmGlobalModal").modal();
- } else {
- $('#tolleform').submit();
- }
-}
-
document.addEventListener("DOMContentLoaded", function () {
moment.locale(LANG);
var slxMoment = moment;
+ var locmap = {{{locmap}}};
var dateSettings = {
format: 'yyyy-mm-dd',
@@ -175,7 +185,8 @@ document.addEventListener("DOMContentLoaded", function () {
$('.datepicker').datepicker(dateSettings);
$('.timepicker2').timepicker(timeSettings);
- $('#locations').multiselect({numberDisplayed: 1});
+ var $locations = $('#locations');
+ $locations.multiselect({numberDisplayed: 1});
var start_date = $('#starttime_date');
var start_time = $('#starttime_time');
@@ -197,54 +208,142 @@ document.addEventListener("DOMContentLoaded", function () {
}
});
+ var examStart, examEnd;
+
var startEndChanged = function () {
- var sd = slxMoment(start_date.val() + ' ' + start_time.val(), 'YYYY-MM-DD H:mm');
- var ed = slxMoment(end_date.val() + ' ' + end_time.val(), 'YYYY-MM-DD H:mm');
- if (!sd.isValid() || !ed.isValid()) {
- rspan.text('-');
+ examStart = slxMoment(start_date.val() + ' ' + start_time.val(), 'YYYY-MM-DD H:mm');
+ examEnd = slxMoment(end_date.val() + ' ' + end_time.val(), 'YYYY-MM-DD H:mm');
+ if (!examStart.isValid() || !examEnd.isValid()) {
+ rspan.text($('#txt-invalid').text()).addClass('text-danger');
+ return;
+ }
+ var diff = examEnd.diff(examStart);
+ if (diff <= 0) {
+ rspan.text($('#txt-reverse').text()).addClass('text-danger');
return;
}
- rspan.text(slxMoment.duration(ed.diff(sd)).humanize());
- // Lecture selection
- $('#lecturelist option').each(function (idx, elem) {
- var e = $(elem);
- var from = e.data('from');
- var to = e.data('to');
- if (!from || !to)
+ rspan.text(slxMoment.duration(diff).humanize()).removeClass('text-danger');
+ updateLectureInfo();
+ };
+
+ var $timespanWarning = $('#warn-range');
+ var $locationWarning = $('#warn-locations');
+ var updateLectureInfo = function() {
+ (function() {
+ var $sel = $('#lecturelist option:selected');
+ var lectureStart = $sel.data('from');
+ var lectureEnd = $sel.data('to');
+ if (!lectureStart || !lectureEnd) {
+ $timespanWarning.hide();
return;
- from = slxMoment.unix(from);
- to = slxMoment.unix(to);
- if (from.isBefore(sd) || to.isAfter(ed)) {
- e.css('color', '#999');
- e.data('inrange', false)
+ }
+ lectureStart = slxMoment.unix(lectureStart);
+ lectureEnd = slxMoment.unix(lectureEnd);
+ var diff;
+ var warnings = [];
+ if (examStart.isBefore(lectureStart)) {
+ warnings.push('.start-before-lecture-start');
+ }
+ if (lectureEnd.diff(lectureStart, 'hours') >= 12) {
+ // Lecture is longer than 12 hours -- only consider exam start/end date outside range
+ if (lectureEnd.diff(examStart, 'minutes') < 15) { // Start after end, or very close to end
+ warnings.push('.start-after-lecture-end');
+ }
+ if (examEnd.diff(lectureEnd, 'minutes') > 15) {
+ warnings.push('.end-after-lecture-end');
+ }
} else {
- e.css('color', '');
- e.data('inrange', true);
+ // Lecture is shorter than 12 hours -- assume it's the actual timespan of the exam
+ if (examStart.diff(lectureStart, 'minutes') > 30) {
+ warnings.push('.start-after-lecture-start');
+ }
+ diff = examEnd.diff(lectureEnd, 'minutes');
+ if (diff > 15) {
+ warnings.push('.end-after-lecture-end');
+ } else if (diff < -30) {
+ warnings.push('.end-before-lecture-end');
+ }
}
- });
- updateLectureInfo();
+ if (warnings.length === 0) {
+ $timespanWarning.hide();
+ return;
+ }
+ $timespanWarning.find('.item').hide();
+ for (var i = 0; i < warnings.length; ++i) {
+ $timespanWarning.find(warnings[i]).show();
+ }
+ $timespanWarning.find('.lecture-range').text(slxMoment.unix($sel.data('from'))
+ .format('LLL') + ' – ' + slxMoment.unix($sel.data('to')).format('LLL'));
+ $timespanWarning.show();
+ })();
+ showHeading();
};
- var updateLectureInfo = function() {
- var sel = $('#lecturelist option:selected');
- if (sel.val() === '' || sel.data('inrange')) {
- $('#lecture-info').text('-');
+ var expandLocs = function(locs) {
+ var ret = locs.map(function(n) { return parseInt(n) });
+ for (var i = 0; i < locs.length; ++i) {
+ if ($.isArray(locmap[locs[i]])) {
+ ret.push(...locmap[locs[i]].filter(function (v, i, s) { return ret.indexOf(v) === -1 }));
+ }
+ }
+ return ret;
+ };
+
+ var updateLocationsInfo = function() {
+ (function() {
+ var selectedLocs = $locations.val();
+ var lecLocs = $('#lecturelist option:selected').data('locations');
+ if (!lecLocs || lecLocs.length === 0 || !selectedLocs) {
+ $locationWarning.hide();
+ return;
+ }
+ lecLocs = ('' + lecLocs).split(',');
+ if (!$.isArray(selectedLocs)) {
+ selectedLocs = [selectedLocs];
+ }
+ selectedLocs = expandLocs(selectedLocs);
+ lecLocs = expandLocs(lecLocs);
+ for (var i = 0; i < selectedLocs.length; ++i) {
+ if (lecLocs.indexOf(selectedLocs[i]) === -1) {
+ $locationWarning.find('.locname').text($locations.find('option[value="' + selectedLocs[i] + '"]').text());
+ $locationWarning.show();
+ return;
+ }
+ }
+ $locationWarning.hide();
+ })();
+ showHeading();
+ };
+
+ $('#saveButton').click(function () {
+ if ($('#locations option:selected').length === 0 && $('#locations option').length > 1) {
+ $("#confirmGlobalModal").modal();
+ } else {
+ $('#tolleform').submit();
+ }
+ });
+
+ var $sanity = $('#sanity-check');
+ var showHeading = function() {
+ if ($locationWarning.is(':visible') || $timespanWarning.is(':visible')) {
+ $sanity.show();
} else {
- $('#lecture-info').text('{{lang_lectureOutOfRange}} (' + slxMoment.unix(sel.data('from')).format('YYYY-MM-DD H:mm') + ' - ' + slxMoment.unix(sel.data('to')).format('YYYY-MM-DD H:mm') + ')');
+ $sanity.hide();
}
};
+ startEndChanged();
+ updateLocationsInfo();
start_date.change(startEndChanged);
start_time.change(startEndChanged);
end_date.change(startEndChanged);
end_time.change(startEndChanged);
- $('#lecturelist').change(updateLectureInfo);
+ $('#lecturelist').change(updateLectureInfo).change(updateLocationsInfo);
+ $locations.change(updateLocationsInfo);
$("#cancelButton").click(function () {
- window.location.replace("?do=exams");
+ window.history.back();
});
- startEndChanged();
-
}, false);
// --></script>
diff --git a/modules-available/exams/templates/page-exams.html b/modules-available/exams/templates/page-exams.html
index 89743c95..085b529a 100644
--- a/modules-available/exams/templates/page-exams.html
+++ b/modules-available/exams/templates/page-exams.html
@@ -18,10 +18,20 @@
</tr>
</thead>
<tbody>
+ {{#decollapse}}
+ <tr class="hidden collapse"></tr><!-- need this right before the slx-decollapse -->
+ <tr class="slx-decollapse">
+ <td colspan="5">
+ <span class="btn-group btn-group-justified">
+ <span class="btn btn-default btn-sm"><span class="glyphicon glyphicon-menu-down"></span></span>
+ </span>
+ </td>
+ </tr>
+ {{/decollapse}}
{{#exams}}
<tr class="{{rowClass}}">
<td>{{examid}}</td>
- <td>
+ <td width="100%">
{{locationnames}}
{{^locationnames}}
<i>{{lang_global}}</i>