diff --git a/CHANGELOG.md b/CHANGELOG.md index c5836333f..ad81239bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [22.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v22.1.0...v22.1.1) (2020-11-14) + +## [22.1.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v22.0.0...v22.1.0) (2020-11-10) + + +### Features + +* partial/conditional downloads & video streaming ([5b28303](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5b28303539e28024b43addb413aedc4e5ee0e470)) + + +### Bug Fixes + +* translation ([80960f4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/80960f42c578c201f78e226653431e9dd965cfce)) +* **personalised-sheet-files:** don't delete files when "keep" ([6008cb0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6008cb040dea268e0a096f6c2fafa87f321d115f)) + +## [22.0.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.1.1...v22.0.0) (2020-11-06) + + +### ⚠ BREAKING CHANGES + +* **html-field:** StoredMarkup + +### Bug Fixes + +* **html-field:** introduce stored-markup ([e25e8a2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/e25e8a2f4ca65afc29acc8a3884df9acf77d4398)) + +### [21.1.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.1.0...v21.1.1) (2020-11-06) + + +### Bug Fixes + +* **course:** better explanation for material access ([78c5bc5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/78c5bc5258c9305deafac18b010dc6a41e5ea864)) + ## [21.1.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v21.0.3...v21.1.0) (2020-11-05) diff --git a/config/settings.yml b/config/settings.yml index 4ded0132e..9c4060e61 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -35,7 +35,7 @@ notification-expiration: 259200 session-timeout: 7200 bearer-expiration: 604800 bearer-encoding: HS256 -maximum-content-length: "_env:MAX_UPLOAD_SIZE:134217728" +maximum-content-length: "_env:MAX_UPLOAD_SIZE:805306368" session-files-expire: 3600 prune-unreferenced-files-within: 57600 prune-unreferenced-files-interval: 3600 diff --git a/config/video-types b/config/video-types new file mode 100644 index 000000000..361fd28ef --- /dev/null +++ b/config/video-types @@ -0,0 +1,31 @@ +# Simple list of mime-types corresponding to video-formats +# +# Comments are empty lines and any line for which the first non-whitespace symbol is ‘#’ +# +# Format is a single mime-type per line (may not contain whitespace) +# +# Largely copied from https://en.wikipedia.org/wiki/Video_file_format + +video/webm +video/x-matroska +video/x-flv +video/x-f4v +video/ogg +video/x-mng +video/x-msvideo +model/vnd.mts +video/quicktime +video/x-ms-wmv +application/vnd.rn-realmedia +application/vnd.rn-realmedia-vbr +video/vnd.vivo +video/x-ms-asf +video/mp4 +video/mpeg +video/x-m4v +video/3gpp +video/3gpp2 +application/mxf +video/h261 +video/h263 +video/h264 \ No newline at end of file diff --git a/frontend/src/app.sass b/frontend/src/app.sass index bbef8618a..7e1414622 100644 --- a/frontend/src/app.sass +++ b/frontend/src/app.sass @@ -1496,3 +1496,17 @@ pre, tt, code .workflow-payload--label font-size: 20px font-weight: 600 + +video + max-width: 100% + max-height: calc(90vh - var(--current-header-height)) + background: black + +.video-container + display: flex + justify-content: center + width: 100% + + & > video + object-fit: contain + flex-grow: 1 diff --git a/frontend/src/utils/inputs/inputs.sass b/frontend/src/utils/inputs/inputs.sass index 022efa71d..a42071863 100644 --- a/frontend/src/utils/inputs/inputs.sass +++ b/frontend/src/utils/inputs/inputs.sass @@ -225,11 +225,15 @@ option margin: 10px 0 color: var(--color-fontsec) +.file-input__list-wrapper + overflow: auto + max-height: 75vh + max-width: 30vw + .file-input__list margin-left: 40px margin-top: 10px font-weight: 600 - max-width: 25vw tr:last-child td padding-bottom: 0 @@ -237,6 +241,7 @@ option .file-input__list-item font-family: var(--font-monospace) font-size: 15px + word-break: break-all // PREVIOUSLY UPLOADED FILES @@ -250,3 +255,13 @@ option .checkbox margin-left: 12px + +.form--vertical .form-group__input + grid-column: unset + grid-row: 2 + +.form-group.form--vertical + grid-template: auto auto / auto + +.form--vertical__cell + vertical-align: top diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg index 93d2e2322..9fb87c738 100644 --- a/messages/uniworx/de-de-formal.msg +++ b/messages/uniworx/de-de-formal.msg @@ -435,7 +435,7 @@ MaterialVisibleFromTip: Ohne Datum nie sichtbar für Teilnehmer; leer lassen ist MaterialVisibleFromEditWarning: Das Datum der Veröffentlichung liegt in der Vergangenheit und sollte nicht mehr verändert werden, da dies die Benutzer verwirren könnte. MaterialInvisible: Dieses Material ist für Teilnehmer momentan unsichtbar! MaterialFiles: Dateien -MaterialHeading materialName@MaterialName: Material "#{materialName}" +MaterialHeading materialName@MaterialName: #{materialName} MaterialListHeading: Materialien MaterialNewHeading: Neues Material veröffentlichen MaterialNewTitle: Neues Material @@ -448,6 +448,9 @@ MaterialDelHasFiles count@Int64: inklusive #{count} #{pluralDE count "Datei" "Da MaterialIsVisible: Achtung, dieses Material wurde bereits veröffentlicht. MaterialDeleted materialName@MaterialName: Material "#{materialName}" gelöscht MaterialArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand materialName@MaterialName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase materialName} +MaterialVideo materialName@MaterialName: #{materialName} - Video +MaterialVideoUnsupported: Ihr Browser scheint keine eingebetten Videos zu unterstützen +MaterialVideoDownload: Herunterladen Unauthorized: Sie haben hierfür keine explizite Berechtigung. @@ -1026,10 +1029,10 @@ MailCourseRegisteredIntroOther displayName@Text courseName@Text termDesc@Text: # MailSubjectExamResult csh@CourseShorthand examn@ExamName: Ergebnisse für #{examn} in #{csh} wurden herausgegeben MailExamResultIntro courseName@Text termDesc@Text examn@ExamName: Sie können nun Ihr Ergebnis für #{examn} im Kurs #{courseName} (#{termDesc}) einsehen. -MailSubjectExamOfficeExamResults csh@CourseShorthand examn@ExamName: Ergebnisse für #{examn} in #{csh} sind fertiggestellt +MailSubjectExamOfficeExamResults coursen@CourseName examn@ExamName: Ergebnisse für #{examn} in #{coursen} sind fertiggestellt MailExamOfficeExamResultsIntro courseName@Text termDesc@Text examn@ExamName: Ein Kursverwalter hat die Prüfungsleistungen für #{examn} im Kurs #{courseName} (#{termDesc}) freigegeben. -MailSubjectExamOfficeExamResultsChanged csh@CourseShorthand examn@ExamName: Ergebnisse für #{examn} in #{csh} wurden verändert +MailSubjectExamOfficeExamResultsChanged coursen@CourseName examn@ExamName: Ergebnisse für #{examn} in #{coursen} wurden verändert MailExamOfficeExamResultsChangedIntro courseName@Text termDesc@Text examn@ExamName: Ein Kursverwalter hat Prüfungsleistungen für #{examn} im Kurs #{courseName} (#{termDesc}) verändert. MailSubjectExamOfficeExternalExamResults coursen@CourseName examn@ExamName: Ergebnisse für #{examn} in #{coursen} @@ -1468,6 +1471,7 @@ BreadcrumbAllocationInfo: Ablauf einer Zentralanmeldung BreadcrumbCourseParticipantInvitation: Einladung zum Kursteilnehmer BreadcrumbMaterialArchive: Archiv BreadcrumbMaterialFile: Datei +BreadcrumbMaterialVideo: Video BreadcrumbSheetArchive: Dateien BreadcrumbSheetIsCorrector: Korrektor-Überprüfung BreadcrumbSheetPseudonym: Pseudonym @@ -1738,6 +1742,10 @@ TutorialParticipants: Teilnehmer TutorialCapacity: Kapazität TutorialFreeCapacity: Freie Plätze TutorialRoom: Regulärer Raum +TutorialRoomHidden: Raum nur für Teilnehmer +TutorialRoomHiddenTip: Soll der Raum nur den Teilnehmern des Tutoriums angezeigt werden? +TutorialRoomIsUnset: — +TutorialRoomIsHidden: Raum wird nur Teilnehmern angezeigt TutorialTime: Zeit TutorialRegistered: Angemeldet TutorialRegGroup: Registrierungs-Gruppe @@ -1839,6 +1847,7 @@ ExamFinished: Ergebnisse sichtbar ab ExamFinishedOffice: Noten bekannt gegeben ExamFinishedParticipant: Bewertung voraussichtlich abgeschlossen ExamFinishedTip: Zeitpunkt zu dem Prüfungergebnisse den Teilnehmern gemeldet werden; ohne Datum werden die Prüfungsergebnisse zunächst nie gemeldet +ExamFinishedTipCloseOnFinished: Zeitpunkt zu dem Prüfungergebnisse den Teilnehmern und den Prüfungsverwaltungen gemeldet werden; ohne Datum werden die Prüfungsergebnisse zunächst nie gemeldet ExamClosed: Noten gemeldet ExamClosedTip: Prüfungsbeauftraget, die im System Noten einsehen, werden zu diesem Zeitpunkt benachrichtigt und danach bei Änderungen informiert ExamGradingMode: Bewertungsmodus @@ -1886,6 +1895,8 @@ ExamRoomSurname': Nach Nachname ExamRoomMatriculation': Nach Matrikelnummer ExamRoomRandom': Zufällig pro Teilnehmer ExamRoomFifo': Auswahl durch Teilnehmer bei Anmeldung +ExamOccurrenceRoomIsUnset: — +ExamOccurrenceRoomIsHidden: Raum wird nur Teilnehmern angezeigt ExamOccurrence: Termin/Raum ExamNoOccurrence: Kein Termin/Raum @@ -1894,6 +1905,8 @@ ExamOccurrences: Termine ExamRooms: Räume ExamTimes: Termine ExamRoomRoom: Raum +ExamRoomRoomHidden: Raum nur für Angemeldete +ExamRoomRoomHiddenTip: Soll der Raum nur zu diesem Termin/Raum angemeldeten Prüfungsteilnehmern angezeigt werden? ExamRoomAlreadyExists: Prüfung ist bereits eingetragen ExamRoomName: Interne Bezeichnung ExamRoomCapacity: Kapazität @@ -2501,6 +2514,7 @@ BtnCloseExam: Prüfung abschließen ExamCloseTip: Wenn eine Prüfung abgeschlossen wird, werden Prüfungsbeauftragte, die im System Noten einsehen, benachrichtigt und danach bei Änderungen informiert. ExamCloseReminder: Bitte schließen Sie die Prüfung frühstmöglich, sobald die Prüfungsleistungen sich voraussichtlich nicht mehr ändern werden. Z.B. direkt nach der Klausureinsicht. ExamDidClose: Prüfung erfolgreich abgeschlossen +ExamCloseTipOnFinished: Die Prüfung wird automatisch abgeschlossen, also Prüfungsbeauftragte, die im System Note einsehen, benachrichtigt und danach bei Änderungen informiert, sobald die Noten für die Prüfungsteilnehmer veröffentlicht werden. ExamClosedSince time@Text: Prüfung abgeschlossen seit #{time} @@ -2561,7 +2575,12 @@ CourseNewsActionDelete: Löschen CourseNewsActionCreate: Neue Nachricht CourseMaterial: Material CourseMaterialFree: Das Kursmaterial ist ohne Anmeldung frei zugänglich -CourseMaterialNotFree: Eine Anmeldung zum Kurs ist Voraussetzung zum Zugang zu Kursmaterial +CourseMaterialNotFree: Das Kursmaterial ist nur für Mitglieder des Kurses einsehbar, also z.B. für Teilnehmer, Tutoren, Korrektoren und Verwalter. + +CourseSheetsFoundHere: Die Übungsblatter zum Kurs finden Sie hier +CourseSheetsNoneVisible: Aktuell gibt es zu diesem Kurs keine Übungsblätter, oder nur Übungsblätter auf die Sie keinen Zugriff haben (z.B. aufgrund von Fristen bzgl. der Sichtbarkeit). +CourseMaterialsFoundHere: Material zum Kurs finden Sie hier +CourseMaterialsNoneVisible: Aktuell gibt es zu diesem Kurs kein Material, oder nur Material auf das Sie keinen Zugriff haben (z.B. aufgrund von Fristen bzgl. der Sichtbarkeit). CourseNewsVisibleFromEditWarning: Das Datum der Veröffentlichung liegt in der Vergangenheit und sollte nicht mehr verändert werden, da dies die Teilnehmer verwirren könnte. CourseNewsVisibleFromTip: Ohne Datum nie sichtbar für Teilnehmer; leer lassen ist nur sinnvoll für noch unfertige Nachrichten @@ -2616,6 +2635,10 @@ CourseEventType: Art CourseEventTypePlaceholder: Vorlesung, Zentralübung, ... CourseEventTime: Zeit CourseEventRoom: Regulärer Raum +CourseEventRoomHidden: Raum nur für Teilnehmer +CourseEventRoomHiddenTip: Soll der Raum nur angemeldeten Kursteilnehmern angezeigt werden? +CourseEventRoomIsUnset: — +CourseEventRoomIsHidden: Raum wird nur Kurs-assoziierten Personen (Teilnehmer, Tutoren, Korrektoren, etc.) angezeigt CourseEventNote: Notiz CourseEventActions: Aktionen CourseEventsActionEdit: Bearbeiten @@ -3069,3 +3092,20 @@ InvalidCredentialsADTooManyContextIds: Benutzereintrag trägt zu viele Sicherhei InvalidCredentialsADAccountExpired: Benutzereintrag abgelaufen InvalidCredentialsADPasswordMustChange: Passwort muss geändert werden InvalidCredentialsADAccountLockedOut: Benutzereintrag wurde durch Eindringlingserkennung gesperrt + +ExamCloseModeSeparate: Separat +ExamCloseModeOnFinished: Mit Veröffentlichung +ExamCloseModeOnFinishedHidden: Mit Veröffentlichung (versteckt) +ExamCloseMode: Prüfungs-Abschluss + +RoomReferenceSimple: Text +RoomReferenceLink: Link & Anweisungen +RoomReferenceSimpleText: Raum +RoomReferenceSimpleTextPlaceholder: Raum +RoomReferenceLinkLink: Link +RoomReferenceLinkLinkPlaceholder: URL +RoomReferenceLinkInstructions: Anweisungen +RoomReferenceLinkInstructionsPlaceholder: Anweisungen +RoomReferenceNone: — + +UrlFieldCouldNotParseAbsolute: Konnte nicht als absolute URL interpretiert werden diff --git a/messages/uniworx/en-eu.msg b/messages/uniworx/en-eu.msg index c9332920a..8814dabf3 100644 --- a/messages/uniworx/en-eu.msg +++ b/messages/uniworx/en-eu.msg @@ -433,7 +433,7 @@ MaterialVisibleFromTip: Never visible to participants if left empty; leaving the MaterialVisibleFromEditWarning: This course material has already been published and should not be edited. Doing so might confuse the participants. MaterialInvisible: This course material is currently invisible to participants! MaterialFiles: Files -MaterialHeading materialName: Course material “#{materialName}” +MaterialHeading materialName: #{materialName} MaterialListHeading: Course materials MaterialNewHeading: Publish new course material MaterialNewTitle: New course material @@ -446,6 +446,9 @@ MaterialDelHasFiles count: including #{count} #{pluralEN count "file" "files"} MaterialIsVisible: Caution, this course material has already been published. MaterialDeleted materialName: Successfully deleted course material “#{materialName}” MaterialArchiveName tid ssh csh materialName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase materialName} +MaterialVideo materialName: #{materialName} - Video +MaterialVideoUnsupported: Your browser does not seem to support embedded video +MaterialVideoDownload: Download Unauthorized: You do not have explicit authorisation. UnauthorizedAnd l r: (#{l} AND #{r}) @@ -1015,10 +1018,10 @@ MailCourseRegisteredIntroOther displayName courseName termDesc: #{displayName} w MailSubjectExamResult csh examn: Results for #{examn} in #{csh} are now available MailExamResultIntro courseName termDesc examn: You may now view your result for #{examn} of the course #{courseName} (#{termDesc}). -MailSubjectExamOfficeExamResults csh examn: Results for #{examn} of #{csh} are now available +MailSubjectExamOfficeExamResults coursen examn: Results for #{examn} of #{coursen} are now available MailExamOfficeExamResultsIntro courseName termDesc examn: A course administrator has made the results for #{examn} of the course #{courseName} (#{termDesc}) available. -MailSubjectExamOfficeExamResultsChanged csh examn: Results for #{examn} of #{csh} were changed +MailSubjectExamOfficeExamResultsChanged coursen examn: Results for #{examn} of #{coursen} were changed MailExamOfficeExamResultsChangedIntro courseName termDesc examn: A course administrator has changed exam results for #{examn} of the course #{courseName} (#{termDesc}). MailSubjectExamOfficeExternalExamResults coursen@CourseName examn@ExamName: Results for #{examn} in #{coursen} @@ -1446,6 +1449,7 @@ BreadcrumbAllocationInfo: On central allocations BreadcrumbCourseParticipantInvitation: Invitation to be a course participant BreadcrumbMaterialArchive: Archive BreadcrumbMaterialFile: File +BreadcrumbMaterialVideo: Video BreadcrumbSheetArchive: Files BreadcrumbSheetIsCorrector: Corrector-check BreadcrumbSheetPseudonym: Pseudonym @@ -1697,6 +1701,10 @@ TutorialParticipants: Participants TutorialCapacity: Capacity TutorialFreeCapacity: Free capacity TutorialRoom: Regular room +TutorialRoomHidden: Room only for participants +TutorialRoomHiddenTip: Should the room only be displayed to tutorial participants? +TutorialRoomIsUnset: — +TutorialRoomIsHidden: Room is only displayed to participants TutorialTime: Time TutorialRegistered: Registered TutorialRegGroup: Registration group @@ -1798,6 +1806,7 @@ ExamFinished: Results visible from ExamFinishedOffice: Exam achievements published ExamFinishedParticipant: Marking expected to be finished ExamFinishedTip: At this participants are informed of their exam achievements. If left empty participants are never informed of their exam achievements. +ExamFinishedTipCloseOnFinished: At this time participants and exam offices are informed of the exam achievements. If left empty participants and exam offices are never informed of the exam achievements. ExamClosed: Exam achievements registered ExamClosedTip: At this time exam offices, which pull exam achievements from Uni2work, are informed. Changes to exam achievements trigger further notifications ExamGradingMode: Grading mode @@ -1845,6 +1854,8 @@ ExamRoomSurname': By surname ExamRoomMatriculation': By matriculation ExamRoomRandom': Randomly ExamRoomFifo': Selected by the participants when registering +ExamOccurrenceRoomIsUnset: — +ExamOccurrenceRoomIsHidden: Room is only displayed to participants registered for this occurrence/room ExamOccurrence: Occurrence/room ExamNoOccurrence: No occurrence/room @@ -1853,6 +1864,8 @@ ExamOccurrences: Exams ExamRooms: Rooms ExamTimes: Times ExamRoomRoom: Room +ExamRoomRoomHidden: Room only for participants +ExamRoomRoomHiddenTip: Should the room only be displayed to participants registered for this occurrence/room? ExamRoomAlreadyExists: Occurrence already configured ExamRoomName: Internal name ExamRoomCapacity: Capacity @@ -2461,6 +2474,7 @@ BtnCloseExam: Close exam ExamCloseTip: When an exam is closed all relevant exam offices, which pull exam achievements from Uni2work, are informed and kept up to date with changes. ExamCloseReminder: Please close the exam as soon as possible, when exam achievements are no longer expected to change e.g. after inspection of the exam has concluced. ExamDidClose: Successfully closed exam +ExamCloseTipOnFinished: The exam will be closed automatically as soon as exam participants are informed of their exam achievements. That means exam offices will be able notified once and after that each time a grade changes. ExamClosedSince time: Exam closed since #{time} @@ -2520,8 +2534,13 @@ CourseNewsActionEdit: Edit CourseNewsActionDelete: Delete CourseNewsActionCreate: Create new item CourseMaterial: Material -CourseMaterialFree: Course material is publicly accessable -CourseMaterialNotFree: Only course participants may access course material +CourseMaterialFree: Course material is publicly accessible +CourseMaterialNotFree: Course material is only accessible to members of the course, e.g. for participants, tutors, correctors or administratiors. + +CourseSheetsFoundHere: Exercise sheets for this course are available here +CourseSheetsNoneVisible: Currently there are no exercise sheets for this course or only exercise sheets to which you don't have access (e.g. because of visibility settings) +CourseMaterialsFoundHere: Material for this course is available here +CourseMaterialsNoneVisible: Currently there is no material for this course or only material to which you don't have access (e.g. because of visibility settings) CourseNewsVisibleFromEditWarning: This item of course news has already been published and should no longer be changed sind this might confuse participants. CourseNewsVisibleFromTip: If left empty this item is never visible. Leave empty for unfinished items @@ -2576,6 +2595,10 @@ CourseEventType: Type CourseEventTypePlaceholder: Lecture, Exercise discussion, ... CourseEventTime: Time CourseEventRoom: Regular room +CourseEventRoomHidden: Room only for participants +CourseEventRoomHiddenTip: Should the room only be displayde to course participants? +CourseEventRoomIsUnset: — +CourseEventRoomIsHidden: Room is only displayed to course associated persons (participants, tutor, correctors, etc.) CourseEventNote: Note CourseEventActions: Actions CourseEventsActionEdit: Edit @@ -2959,3 +2982,20 @@ InvalidCredentialsADTooManyContextIds: Account carries to many security identifi InvalidCredentialsADAccountExpired: Account expired InvalidCredentialsADPasswordMustChange: Password needs to be changed InvalidCredentialsADAccountLockedOut: Account disabled by intruder detection + +ExamCloseModeSeparate: Seperately +ExamCloseModeOnFinished: With publication of achievements +ExamCloseModeOnFinishedHidden: With publication of achievements (hidden) +ExamCloseMode: Exam closure + +RoomReferenceSimple: Text +RoomReferenceLink: Link & Instructions +RoomReferenceSimpleText: Room +RoomReferenceSimpleTextPlaceholder: Room +RoomReferenceLinkLink: Link +RoomReferenceLinkLinkPlaceholder: URL +RoomReferenceLinkInstructions: Instructions +RoomReferenceLinkInstructionsPlaceholder: Instructions +RoomReferenceNone: — + +UrlFieldCouldNotParseAbsolute: Could not parse as an absolute URL diff --git a/models/allocations.model b/models/allocations.model index 0bbebbea5..e4e96f6b2 100644 --- a/models/allocations.model +++ b/models/allocations.model @@ -3,8 +3,8 @@ Allocation -- attributes with prefix staff- affect lecturers only, but are invis school SchoolId -- school that manages this central allocation, not necessarily school of courses shorthand AllocationShorthand -- practical shorthand name AllocationName - description Html Maybe -- description for prospective students - staffDescription Html Maybe -- description seen by prospective lecturers only + description StoredMarkup Maybe -- description for prospective students + staffDescription StoredMarkup Maybe -- description seen by prospective lecturers only staffRegisterFrom UTCTime Maybe -- lectureres may register courses staffRegisterTo UTCTime Maybe -- course registration stops -- staffDeregisterUntil not needed: staff may make arbitrary changes until staffRegisterTo, always frozen afterwards diff --git a/models/courses.model b/models/courses.model index 6033ff0a9..d64ec14ac 100644 --- a/models/courses.model +++ b/models/courses.model @@ -5,8 +5,8 @@ DegreeCourse json -- for which degree programmes this course is appropriate fo UniqueDegreeCourse course degree terms Course -- Information about a single course; contained info is always visible to all users name (CI Text) - description Html Maybe -- user-defined large Html, ought to contain module description - linkExternal Text Maybe -- arbitrary user-defined url for external course page + description StoredMarkup Maybe -- user-defined large Html, ought to contain module description + linkExternal URI Maybe -- arbitrary user-defined url for external course page shorthand (CI Text) -- practical shorthand of course name, used for identification term TermId -- semester this course is taught school SchoolId @@ -21,7 +21,7 @@ Course -- Information about a single course; contained info is always visible registerSecret Text Maybe -- enrolement maybe protected by a simple common passphrase materialFree Bool -- False: only enrolled users may see course materials not stored in this table applicationsRequired Bool default=false - applicationsInstructions Html Maybe + applicationsInstructions StoredMarkup Maybe applicationsText Bool default=false applicationsFiles UploadMode "default='{\"mode\": \"no-upload\"}'::jsonb" applicationsRatingsVisible Bool default=false @@ -31,9 +31,10 @@ Course -- Information about a single course; contained info is always visible CourseEvent type (CI Text) course CourseId - room Text + room RoomReference Maybe + roomHidden Bool default=false time Occurrences - note Html Maybe + note StoredMarkup Maybe lastChanged UTCTime default=now() CourseAppInstructionFile @@ -72,7 +73,7 @@ CourseParticipant -- course enrolement CourseUserNote -- lecturers of a specific course may share a text note on each enrolled student course CourseId user UserId - note Html -- arbitrary user-defined text; visible only to lecturer of this course + note StoredMarkup -- arbitrary user-defined text; visible only to lecturer of this course UniqueCourseUserNote user course CourseUserNoteEdit -- who edited a participants course note when user UserId diff --git a/models/courses/materials.model b/models/courses/materials.model index fd1d19fb7..3a4767ec5 100644 --- a/models/courses/materials.model +++ b/models/courses/materials.model @@ -2,7 +2,7 @@ Material -- course material for disemination to course participants course CourseId name (CI Text) type (CI Text) Maybe - description Html Maybe + description StoredMarkup Maybe visibleFrom UTCTime Maybe -- Invisible to enrolled participants before lastEdit UTCTime UniqueMaterial course name diff --git a/models/courses/news.model b/models/courses/news.model index fdd2c0254..c31312d2e 100644 --- a/models/courses/news.model +++ b/models/courses/news.model @@ -3,8 +3,8 @@ CourseNews visibleFrom UTCTime Maybe participantsOnly Bool title Text Maybe - content Html - summary Html Maybe + content StoredMarkup + summary StoredMarkup Maybe lastEdit UTCTime CourseNewsFile news CourseNewsId diff --git a/models/exams.model b/models/exams.model index 4963e4075..1f496f43a 100644 --- a/models/exams.model +++ b/models/exams.model @@ -16,7 +16,7 @@ Exam closed UTCTime Maybe -- Prüfungsamt hat Einsicht (notification) publicStatistics Bool gradingMode ExamGradingMode - description Html Maybe + description StoredMarkup Maybe examMode ExamMode staff Text Maybe UniqueExam course name @@ -31,11 +31,12 @@ ExamPart ExamOccurrence exam ExamId name ExamOccurrenceName - room Text + room RoomReference Maybe + roomHidden Bool default=false capacity Natural start UTCTime end UTCTime Maybe - description Html Maybe + description StoredMarkup Maybe UniqueExamOccurrence exam name ExamRegistration exam ExamId diff --git a/models/schools.model b/models/schools.model index 950b1c624..af9e54889 100644 --- a/models/schools.model +++ b/models/schools.model @@ -7,6 +7,7 @@ School json examMinimumRegisterDuration NominalDiffTime Maybe examRequireModeForRegistration Bool default=false examDiscouragedModes ExamModeDNF + examCloseMode ExamCloseMode default='separate' UniqueSchool name UniqueSchoolShorthand shorthand -- required for Normalisation of CI Text Primary shorthand -- newtype Key School = SchoolKey { unSchoolKey :: SchoolShorthand } diff --git a/models/sheets.model b/models/sheets.model index f54426040..6f0bb6176 100644 --- a/models/sheets.model +++ b/models/sheets.model @@ -1,10 +1,10 @@ Sheet -- exercise sheet for a given course course CourseId name (CI Text) - description Html Maybe + description StoredMarkup Maybe type SheetType -- Does it count towards overall course grade? grouping SheetGroup -- May participants submit in groups of certain sizes? - markingText Html Maybe -- Instructons for correctors, included in marking templates + markingText StoredMarkup Maybe -- Instructons for correctors, included in marking templates visibleFrom UTCTime Maybe -- Invisible to enrolled participants before activeFrom UTCTime Maybe -- Download of questions and submission is permitted afterwards activeTo UTCTime Maybe -- Submission is only permitted before diff --git a/models/system-messages.model b/models/system-messages.model index 0d5cd5611..e8dfbd9ad 100644 --- a/models/system-messages.model +++ b/models/system-messages.model @@ -11,14 +11,14 @@ SystemMessage lastChanged UTCTime default=now() lastUnhide UTCTime default=now() defaultLanguage Lang -- Language of @content@ and @summary@ - content Html -- Detailed message shown when clicking on the @summary@-popup or when no @summary@ is specified - summary Html Maybe + content StoredMarkup -- Detailed message shown when clicking on the @summary@-popup or when no @summary@ is specified + summary StoredMarkup Maybe SystemMessageTranslation -- Translation of a @SystemMessage@ into another language; which language to choose is determined by user-sent HTTP-headers message SystemMessageId language Lang - content Html - summary Html Maybe + content StoredMarkup + summary StoredMarkup Maybe UniqueSystemMessageTranslation message language SystemMessageHidden diff --git a/models/tutorials.model b/models/tutorials.model index 90066fcb1..d193ff5d5 100644 --- a/models/tutorials.model +++ b/models/tutorials.model @@ -3,7 +3,8 @@ Tutorial json course CourseId type (CI Text) -- "Tutorium", "Zentralübung", ... capacity Int Maybe -- limit for enrolment in this tutorial - room Text Maybe + room RoomReference Maybe + roomHidden Bool default=false time Occurrences regGroup (CI Text) Maybe -- each participant may register for one tutorial per regGroup registerFrom UTCTime Maybe diff --git a/models/workflows.model b/models/workflows.model index e3f258ecd..590b79744 100644 --- a/models/workflows.model +++ b/models/workflows.model @@ -9,14 +9,14 @@ WorkflowDefinitionDescription definition WorkflowDefinitionId language Lang title Text - description Html Maybe + description StoredMarkup Maybe UniqueWorkflowDefinitionDescription definition language WorkflowDefinitionInstanceDescription definition WorkflowDefinitionId language Lang title Text - description Html Maybe + description StoredMarkup Maybe UniqueWorkflowDefinitionInstanceDescription definition language WorkflowInstance @@ -31,7 +31,7 @@ WorkflowInstanceDescription instance WorkflowInstanceId language Lang title Text - description Html Maybe + description StoredMarkup Maybe UniqueWorkflowInstanceDescription instance language WorkflowWorkflow diff --git a/package-lock.json b/package-lock.json index acad56503..992dc44c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "21.1.0", + "version": "22.1.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e573d0ac6..e7f198ec4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "21.1.0", + "version": "22.1.1", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index ef2c4c728..6328dde4f 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 21.1.0 +version: 22.1.1 dependencies: - base @@ -8,6 +8,7 @@ dependencies: - yesod-auth - yesod-static - yesod-form + - yesod-persistent - classy-prelude - classy-prelude-yesod - bytestring @@ -157,6 +158,7 @@ dependencies: - list-t - insert-ordered-containers - topograph + - network-uri other-extensions: - GeneralizedNewtypeDeriving @@ -318,6 +320,7 @@ tests: - http-types - yesod-persistent - quickcheck-io + - network-arbitrary ghc-options: - -fno-warn-orphans - -threaded -rtsopts "-with-rtsopts=-N -T" diff --git a/routes b/routes index f3bc11243..e76b6eb4b 100644 --- a/routes +++ b/routes @@ -204,6 +204,7 @@ /show MShowR GET !timeANDcourse-registered !timeANDmaterialsANDcourse-time !corrector !tutor !/download MArchiveR GET !timeANDcourse-registered !timeANDmaterialsANDcourse-time !corrector !tutor !/download/*FilePath MFileR GET !timeANDcourse-registered !timeANDmaterialsANDcourse-time !corrector !tutor + /video/#CryptoUUIDMaterialFile MVideoR GET !timeANDcourse-registered !timeANDmaterialsANDcourse-time !corrector !tutor /tuts CTutorialListR GET !tutor -- THIS route is used to check for overall course tutor access! /tuts/new CTutorialNewR GET POST /tuts/#TutorialName TutorialR: diff --git a/src/CryptoID.hs b/src/CryptoID.hs index 4183bf75d..96e022d2a 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -75,6 +75,7 @@ decCryptoIDs [ ''SubmissionId , ''ExternalExamId , ''WorkflowInstanceId , ''WorkflowWorkflowId + , ''MaterialFileId ] type instance CryptoIDNamespace a WorkflowStateIndex = "WorkflowStateIndex" diff --git a/src/Database/Esqueleto/Utils.hs b/src/Database/Esqueleto/Utils.hs index 66061ec3e..9a5412cfa 100644 --- a/src/Database/Esqueleto/Utils.hs +++ b/src/Database/Esqueleto/Utils.hs @@ -20,12 +20,12 @@ module Database.Esqueleto.Utils , selectExists, selectNotExists , SqlHashable , sha256 - , maybe, maybeEq, unsafeCoalesce + , maybe, maybe2, maybeEq, unsafeCoalesce , bool , max, min , abs , SqlProject(..) - , (->.) + , (->.), (#>>.) , fromSqlKey , selectCountRows , selectMaybe @@ -302,6 +302,20 @@ maybe onNothing onJust val = E.case_ ] (E.else_ onNothing) +maybe2 :: (PersistField a, PersistField b, PersistField c) + => E.SqlExpr (E.Value c) + -> (E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value b) -> E.SqlExpr (E.Value c)) + -> E.SqlExpr (E.Value (Maybe a)) + -> E.SqlExpr (E.Value (Maybe b)) + -> E.SqlExpr (E.Value c) +maybe2 onNothing onJust val1 val2 = E.case_ + [ E.when_ + (isJust val1 E.&&. isJust val2) + E.then_ + (onJust (E.veryUnsafeCoerceSqlExprValue val1) (E.veryUnsafeCoerceSqlExprValue val2)) + ] + (E.else_ onNothing) + infix 4 `maybeEq` maybeEq :: PersistField a @@ -350,23 +364,32 @@ unsafeCoalesce :: E.PersistField a => [E.SqlExpr (E.Value (Maybe a))] -> E.SqlEx unsafeCoalesce = E.veryUnsafeCoerceSqlExprValue . E.coalesce -class (PersistEntity entity, PersistField value) => SqlProject entity value entity' value' | entity value entity' -> value', entity value value' -> entity' where +class (PersistEntity entity, PersistField value, PersistField value') => SqlProject entity value entity' value' | entity value entity' -> value', entity value value' -> entity' where sqlProject :: E.SqlExpr entity' -> EntityField entity value -> E.SqlExpr (E.Value value') unSqlProject :: forall p1 p2. p1 entity -> p2 entity' -> value -> value' + unSqlProjectExpr :: forall p1 p2. p1 entity -> p2 entity' -> E.SqlExpr (E.Value value) -> E.SqlExpr (E.Value value') instance (PersistEntity val, PersistField typ) => SqlProject val typ (E.Entity val) typ where sqlProject = (E.^.) unSqlProject _ _ = id + unSqlProjectExpr _ _ = id instance (PersistEntity val, PersistField typ) => SqlProject val typ (Maybe (E.Entity val)) (Maybe typ) where sqlProject = (E.?.) unSqlProject _ _ = Just + unSqlProjectExpr _ _ = E.just infixl 8 ->. (->.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value b) (->.) expr t = E.unsafeSqlBinOp "->" expr $ E.val t +infixl 8 #>>. + +(#>>.) :: E.SqlExpr (E.Value a) -> Text -> E.SqlExpr (E.Value (Maybe Text)) +(#>>.) expr t = E.unsafeSqlBinOp "#>>" expr $ E.val t + + fromSqlKey :: (ToBackendKey SqlBackend entity, PersistField (Key entity)) => E.SqlExpr (E.Value (Key entity)) -> E.SqlExpr (E.Value Int64) fromSqlKey = E.veryUnsafeCoerceSqlExprValue diff --git a/src/Foundation/I18n.hs b/src/Foundation/I18n.hs index 42a820930..2ebf6e554 100644 --- a/src/Foundation/I18n.hs +++ b/src/Foundation/I18n.hs @@ -230,11 +230,14 @@ embedRenderMessage ''UniWorX ''ExamSynchronicityPreset id embedRenderMessage ''UniWorX ''ExamRequiredEquipmentPreset id embedRenderMessage ''UniWorX ''ChangelogItemKind id embedRenderMessage ''UniWorX ''WorkflowScope' $ ("WorkflowScopeKind" <>) . concat . drop 1 . splitCamel . fromMaybe (error "Expected WorkflowScope' to have '") . stripSuffix "'" +embedRenderMessage ''UniWorX ''RoomReference' $ dropSuffix "'" embedRenderMessage ''UniWorX ''AuthenticationMode id embedRenderMessage ''UniWorX ''RatingValidityException id +embedRenderMessage ''UniWorX ''UrlFieldMessage id + embedRenderMessageVariant ''UniWorX ''ADInvalidCredentials ("InvalidCredentials" <>) newtype ShortSex = ShortSex Sex @@ -326,6 +329,15 @@ instance RenderMessage UniWorX CourseParticipantState where mr :: RenderMessage UniWorX msg => msg -> Text mr = renderMessage foundation ls +instance RenderMessage UniWorX ExamCloseMode where + renderMessage foundation ls = \case + ExamCloseSeparate -> mr MsgExamCloseModeSeparate + ExamCloseOnFinished False -> mr MsgExamCloseModeOnFinished + ExamCloseOnFinished True -> mr MsgExamCloseModeOnFinishedHidden + where + mr :: RenderMessage UniWorX msg => msg -> Text + mr = renderMessage foundation ls + -- ToMessage instances for converting raw numbers to Text (no internationalization) instance ToMessage Int where diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index 0a9c24d57..60e384ad5 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -291,6 +291,7 @@ instance BearerAuthSite UniWorX => YesodBreadcrumbs UniWorX where MDelR -> i18nCrumb MsgMenuMaterialDelete . Just $ CMaterialR tid ssh csh mnm MShowR MArchiveR -> i18nCrumb MsgBreadcrumbMaterialArchive . Just $ CMaterialR tid ssh csh mnm MShowR MFileR _ -> i18nCrumb MsgBreadcrumbMaterialFile . Just $ CMaterialR tid ssh csh mnm MShowR + MVideoR _ -> i18nCrumb MsgBreadcrumbMaterialVideo . Just $ CMaterialR tid ssh csh mnm MShowR breadcrumb (CourseR tid ssh csh CPersonalFilesR) = i18nCrumb MsgBreadcrumbCourseSheetPersonalisedFiles . Just $ CourseR tid ssh csh CShowR diff --git a/src/Foundation/SiteLayout.hs b/src/Foundation/SiteLayout.hs index 8ca6c3b29..9f151bca7 100644 --- a/src/Foundation/SiteLayout.hs +++ b/src/Foundation/SiteLayout.hs @@ -454,7 +454,7 @@ applySystemMessages = liftHandler . maybeT_ . catchMPlus (Proxy @CryptoIDError) case summary of Just s -> addMessageWidget systemMessageSeverity $ msgModal (toWidget s) (Left . SomeRoute $ MessageR cID) - Nothing -> addMessage systemMessageSeverity content + Nothing -> addMessage systemMessageSeverity $ toHtml content tellRegisteredCookieJson CookieSystemMessageState . MergeHashMap $ HashMap.singleton cID mempty{ userSystemMessageShown = Just now } diff --git a/src/Foundation/Yesod/Middleware.hs b/src/Foundation/Yesod/Middleware.hs index 311321ae8..c392a9245 100644 --- a/src/Foundation/Yesod/Middleware.hs +++ b/src/Foundation/Yesod/Middleware.hs @@ -164,6 +164,7 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) . , verifyCourseApplication , verifyCourseNews , verifyWorkflowWorkflow + , verifyMaterialVideo ] where normalizeRender :: Route UniWorX -> WriterT Any (ReaderT SqlReadBackend (HandlerFor UniWorX)) (Route UniWorX) @@ -291,3 +292,12 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) . let newRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID wwR) tell . Any $ route /= newRoute return newRoute + verifyMaterialVideo = maybeOrig $ \route -> do + CMaterialR _tid _ssh _csh _mnm (MVideoR cID) <- return route + mfId <- decrypt cID + MaterialFile{materialFileMaterial} <- lift . lift $ get404 mfId + Material{materialName, materialCourse} <- lift . lift $ get404 materialFileMaterial + Course{courseTerm, courseSchool, courseShorthand} <- lift . lift $ get404 materialCourse + let newRoute = CMaterialR courseTerm courseSchool courseShorthand materialName (MVideoR cID) + tell . Any $ route /= newRoute + return newRoute diff --git a/src/Handler/Course/Edit.hs b/src/Handler/Course/Edit.hs index d1a340740..5c58a0385 100644 --- a/src/Handler/Course/Edit.hs +++ b/src/Handler/Course/Edit.hs @@ -34,14 +34,14 @@ data CourseForm = CourseForm , cfShort :: CourseShorthand , cfSchool :: SchoolId , cfTerm :: TermId - , cfDesc :: Maybe Html - , cfLink :: Maybe Text + , cfDesc :: Maybe StoredMarkup + , cfLink :: Maybe URI , cfVisFrom :: Maybe UTCTime , cfVisTo :: Maybe UTCTime , cfMatFree :: Bool , cfAllocation :: Maybe AllocationCourseForm , cfAppRequired :: Bool - , cfAppInstructions :: Maybe Html + , cfAppInstructions :: Maybe StoredMarkup , cfAppInstructionFiles :: Maybe FileUploads , cfAppText :: Bool , cfAppFiles :: UploadMode @@ -292,7 +292,7 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB <*> areq termsField (fslI MsgCourseSemester) (cfTerm <$> template) <*> aopt htmlField (fslpI MsgCourseDescription (mr MsgCourseDescriptionPlaceholder)) (cfDesc <$> template) - <*> aopt (urlField & cfStrip) (fslpI MsgCourseHomepageExternal (mr MsgCourseHomepageExternalPlaceholder)) + <*> aopt urlField (fslpI MsgCourseHomepageExternal (mr MsgCourseHomepageExternalPlaceholder)) (cfLink <$> template) <*> aopt utcTimeField (fslpI MsgCourseVisibleFrom (mr MsgDate) & setTooltip MsgCourseVisibleFromTip) (deepAlt (cfVisFrom <$> template) newVisFrom) diff --git a/src/Handler/Course/Events/Delete.hs b/src/Handler/Course/Events/Delete.hs index 3dd5d06fb..65c742bbf 100644 --- a/src/Handler/Course/Events/Delete.hs +++ b/src/Handler/Course/Events/Delete.hs @@ -27,7 +27,8 @@ postCEvDeleteR tid ssh csh cID = do [whamlet| $newline never #{courseEventType} - , #{courseEventRoom} + $maybe room <- courseEventRoom + , #{roomReferenceText room} : ^{occurrencesWidget courseEventTime} |] diff --git a/src/Handler/Course/Events/Edit.hs b/src/Handler/Course/Events/Edit.hs index 0dcfaa30a..19390889d 100644 --- a/src/Handler/Course/Events/Edit.hs +++ b/src/Handler/Course/Events/Edit.hs @@ -23,6 +23,7 @@ postCEvEditR tid ssh csh cID = do { courseEventCourse , courseEventType = cefType , courseEventRoom = cefRoom + , courseEventRoomHidden = cefRoomHidden , courseEventTime = cefTime , courseEventNote = cefNote , courseEventLastChanged = now diff --git a/src/Handler/Course/Events/Form.hs b/src/Handler/Course/Events/Form.hs index ecc01b8e9..1f3eb88bc 100644 --- a/src/Handler/Course/Events/Form.hs +++ b/src/Handler/Course/Events/Form.hs @@ -13,9 +13,10 @@ import qualified Database.Esqueleto as E data CourseEventForm = CourseEventForm { cefType :: CI Text - , cefRoom :: Text + , cefRoom :: Maybe RoomReference + , cefRoomHidden :: Bool , cefTime :: Occurrences - , cefNote :: Maybe Html + , cefNote :: Maybe StoredMarkup } courseEventForm :: Maybe CourseEventForm -> Form CourseEventForm @@ -30,16 +31,17 @@ courseEventForm template = identifyForm FIDCourseEvent . renderWForm FormStandar return event ) let courseEventTypes = optionsPairs [ (courseEventType, courseEventType) | Entity _ CourseEvent{..} <- existingEvents ] - courseEventRooms = optionsPairs [ (courseEventRoom, courseEventRoom) | Entity _ CourseEvent{..} <- existingEvents ] cefType' <- wreq (textField & cfStrip & cfCI & addDatalist courseEventTypes) (fslI MsgCourseEventType & addPlaceholder (mr MsgCourseEventTypePlaceholder)) (cefType <$> template) - cefRoom' <- wreq (textField & cfStrip & addDatalist courseEventRooms) (fslI MsgCourseEventRoom) (cefRoom <$> template) + cefRoom' <- aFormToWForm $ roomReferenceFormOpt (fslI MsgCourseEventRoom) (cefRoom <$> template) + cefRoomHidden' <- wpopt checkBoxField (fslI MsgCourseEventRoomHidden & setTooltip MsgCourseEventRoomHiddenTip) (cefRoomHidden <$> template) cefTime' <- aFormToWForm $ occurrencesAForm ("time" :: Text) (cefTime <$> template) cefNote' <- wopt htmlField (fslI MsgCourseEventNote) (cefNote <$> template) return $ CourseEventForm <$> cefType' <*> cefRoom' + <*> cefRoomHidden' <*> cefTime' <*> cefNote' @@ -47,6 +49,7 @@ courseEventToForm :: CourseEvent -> CourseEventForm courseEventToForm CourseEvent{..} = CourseEventForm { cefType = courseEventType , cefRoom = courseEventRoom + , cefRoomHidden = courseEventRoomHidden , cefTime = courseEventTime , cefNote = courseEventNote } diff --git a/src/Handler/Course/Events/New.hs b/src/Handler/Course/Events/New.hs index b389de9de..d2800db1e 100644 --- a/src/Handler/Course/Events/New.hs +++ b/src/Handler/Course/Events/New.hs @@ -21,6 +21,7 @@ postCEventsNewR tid ssh csh = do { courseEventCourse = cid , courseEventType = cefType , courseEventRoom = cefRoom + , courseEventRoomHidden = cefRoomHidden , courseEventTime = cefTime , courseEventNote = cefNote , courseEventLastChanged = now diff --git a/src/Handler/Course/List.hs b/src/Handler/Course/List.hs index a996fc3e5..2ab517fce 100644 --- a/src/Handler/Course/List.hs +++ b/src/Handler/Course/List.hs @@ -81,7 +81,12 @@ makeCourseTable whereClause colChoices psValidator = do return (course, participants, registered, school) lecturerQuery cid (user `E.InnerJoin` lecturer) = do E.on $ user E.^. UserId E.==. lecturer E.^. LecturerUser - E.where_ $ cid E.==. lecturer E.^. LecturerCourse E.&&. lecturer E.^. LecturerType E.==. E.val CourseLecturer + E.where_ $ cid E.==. lecturer E.^. LecturerCourse + E.&&. lecturer E.^. LecturerType E.==. E.val CourseLecturer + return user + isCourseAdminQuery cid (user `E.InnerJoin` lecturer) = do + E.on $ user E.^. UserId E.==. lecturer E.^. LecturerUser + E.where_ $ cid E.==. lecturer E.^. LecturerCourse return user isEditorQuery course user = E.where_ $ mayEditCourse' muid ata course E.&&. E.just (user E.^. UserId) E.==. E.val muid @@ -135,8 +140,8 @@ makeCourseTable whereClause colChoices psValidator = do , ( "lecturer", FilterColumn $ \(course `E.InnerJoin` _school :: CourseTableExpr) criterias -> if | Set.null criterias -> E.val True :: E.SqlExpr (E.Value Bool) | otherwise -> E.exists $ E.from $ \t -> do - user <- lecturerQuery (course E.^. CourseId) t - E.where_ $ E.any (E.hasInfix (user E.^. UserSurname) . E.val) (criterias :: Set.Set Text) + user <- isCourseAdminQuery (course E.^. CourseId) t + E.where_ $ E.any (E.hasInfix (user E.^. UserDisplayName) . E.val) (criterias :: Set.Set Text) ) , ( "openregistration", FilterColumn $ \(course `E.InnerJoin` _school :: CourseTableExpr) criterion -> case getLast (criterion :: Last Bool) of Nothing -> E.val True @@ -167,7 +172,7 @@ makeCourseTable whereClause colChoices psValidator = do Nothing -> E.val True :: E.SqlExpr (E.Value Bool) Just needle -> (E.castString (course E.^. CourseName) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%)) E.||. (E.castString (course E.^. CourseShorthand) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%)) - E.||. (E.castString (course E.^. CourseDescription) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%)) + E.||. (E.maybe (E.val mempty) (E.castString . esqueletoMarkupOutput) (course E.^. CourseDescription) `E.ilike` (E.%) E.++. E.val needle E.++. (E.%)) ) ] , dbtFilterUI = \mPrev -> mconcat $ catMaybes diff --git a/src/Handler/Course/News/Form.hs b/src/Handler/Course/News/Form.hs index 5d5aeb599..33e9a1938 100644 --- a/src/Handler/Course/News/Form.hs +++ b/src/Handler/Course/News/Form.hs @@ -12,8 +12,8 @@ import qualified Data.Conduit.List as C data CourseNewsForm = CourseNewsForm { cnfTitle :: Maybe Text - , cnfSummary :: Maybe Html - , cnfContent :: Html + , cnfSummary :: Maybe StoredMarkup + , cnfContent :: StoredMarkup , cnfParticipantsOnly :: Bool , cnfVisibleFrom :: Maybe UTCTime , cnfFiles :: Maybe FileUploads diff --git a/src/Handler/Course/Show.hs b/src/Handler/Course/Show.hs index f34d5048f..022a35559 100644 --- a/src/Handler/Course/Show.hs +++ b/src/Handler/Course/Show.hs @@ -8,14 +8,16 @@ import Import import Utils.Course import Utils.Form import Handler.Utils -import qualified Database.Esqueleto.Utils as E -import Database.Esqueleto.Utils.TH +import Handler.Utils.Course +import Handler.Utils.Tutorial import qualified Data.CaseInsensitive as CI import qualified Data.Map as Map import qualified Database.Esqueleto as E +import qualified Database.Esqueleto.Utils as E +import Database.Esqueleto.Utils.TH import Handler.Course.Register @@ -28,7 +30,7 @@ getCShowR :: TermId -> SchoolId -> CourseShorthand -> Handler Html getCShowR tid ssh csh = do mbAid <- maybeAuthId now <- liftIO getCurrentTime - (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister) <- runDB . maybeT notFound $ do + (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister,(mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) <- runDB . maybeT notFound $ do [(E.Entity cid course, E.Value courseVisible, E.Value schoolName, E.Value participants, fmap entityVal -> registration, E.Value hasAllocationRegistrationOpen)] <- lift . E.select . E.from $ \((school `E.InnerJoin` course) `E.LeftOuterJoin` participant) -> do @@ -90,10 +92,13 @@ getCShowR tid ssh csh = do lastEditText <- formatTime SelFormatDateTime $ maybe id max (guardOn visible =<< courseNewsVisibleFrom) courseNewsLastEdit mayEditNews <- hasWriteAccessTo $ CNewsR tid ssh csh cID CNEditR mayDelete <- hasWriteAccessTo $ CNewsR tid ssh csh cID CNDeleteR + return (cID, n, visible, files, lastEditText, mayEditNews, mayDelete) - events' <- fmap (sortOn $ courseEventTime . entityVal) . lift $ selectList [ CourseEventCourse ==. cid ] [] - events <- mapM (\(Entity evId ev) -> (, ev) <$> encrypt evId) events' + events' <- fmap (sortOn $ courseEventTime . entityVal . view _1) . lift . E.select . E.from $ \courseEvent -> do + E.where_ $ courseEvent E.^. CourseEventCourse E.==. E.val cid + return (courseEvent, maybe E.false (flip showCourseEventRoom courseEvent . E.val) mbAid) + events <- mapM (\(Entity evId ev, E.Value showRoom) -> (, ev, showRoom) <$> encrypt evId) events' hasSubmissionGroups <- lift . E.selectExists . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup) -> do E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId @@ -108,7 +113,19 @@ getCShowR tid ssh csh = do mayReRegister <- lift . courseMayReRegister $ Entity cid course - return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister) + mayViewSheets <- hasReadAccessTo $ CourseR tid ssh csh SheetListR + sheets <- lift . E.select . E.from $ \sheet -> do + E.where_ $ sheet E.^. SheetCourse E.==. E.val cid + return $ sheet E.^. SheetName + mayViewAnySheet <- anyM sheets $ \(E.Value shn) -> hasReadAccessTo $ CSheetR tid ssh csh shn SShowR + + mayViewMaterials <- hasReadAccessTo $ CourseR tid ssh csh MaterialListR + materials <- lift . E.select . E.from $ \material -> do + E.where_ $ material E.^. MaterialCourse E.==. E.val cid + return $ material E.^. MaterialName + mayViewAnyMaterial <- anyM materials $ \(E.Value mnm) -> hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR + + return (cid,course,courseVisible,schoolName,participants,registration,lecturers,assistants,correctors,tutors,mAllocation,hasApplicationTemplate,mApplication,news,events,submissionGroup,hasAllocationRegistrationOpen,mayReRegister, (mayViewSheets, mayViewAnySheet), (mayViewMaterials, mayViewAnyMaterial)) let mDereg' = maybe id min (allocationOverrideDeregister =<< mAllocation) <$> courseDeregisterUntil course mDereg <- traverse (formatTime SelFormatDateTime) mDereg' @@ -134,15 +151,19 @@ getCShowR tid ssh csh = do let tutorialDBTable = DBTable{..} where + resultTutorial :: Lens' (DBRow (Entity Tutorial, Bool)) (Entity Tutorial) + resultTutorial = _dbrOutput . _1 + resultShowRoom = _dbrOutput . _2 + dbtSQLQuery tutorial = do E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid - return tutorial + return (tutorial, maybe E.false (flip showTutorialRoom tutorial . E.val) mbAid) dbtRowKey = (E.^. TutorialId) - dbtProj = return + dbtProj = traverse $ return . over _2 E.unValue dbtColonnade = dbColonnade $ mconcat - [ sortable (Just "type") (i18nCell MsgTutorialType) $ \DBRow{ dbrOutput = Entity _ Tutorial{..} } -> textCell $ CI.original tutorialType - , sortable (Just "name") (i18nCell MsgTutorialName) $ \DBRow{ dbrOutput = Entity _ Tutorial{..} } -> indicatorCell <> anchorCell (CTutorialR tid ssh csh tutorialName TUsersR) [whamlet|#{tutorialName}|] - , sortable (Just "tutors") (i18nCell MsgTutorialTutors) $ \DBRow{ dbrOutput = Entity tutid _ } -> sqlCell $ do + [ sortable (Just "type") (i18nCell MsgTutorialType) $ \(view $ resultTutorial . _entityVal -> Tutorial{..}) -> textCell $ CI.original tutorialType + , sortable (Just "name") (i18nCell MsgTutorialName) $ \(view $ resultTutorial . _entityVal -> Tutorial{..}) -> indicatorCell <> anchorCell (CTutorialR tid ssh csh tutorialName TUsersR) [whamlet|#{tutorialName}|] + , sortable (Just "tutors") (i18nCell MsgTutorialTutors) $ \(view $ resultTutorial . _entityKey -> tutid) -> sqlCell $ do tutTutors <- fmap (map $(unValueN 3)) . E.select . E.from $ \(tutor `E.InnerJoin` user) -> do E.on $ tutor E.^. TutorUser E.==. user E.^. UserId E.where_ $ tutor E.^. TutorTutorial E.==. E.val tutid @@ -154,12 +175,14 @@ getCShowR tid ssh csh = do