diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b5266122..e679e1592 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,182 @@ 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. +## [25.20.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.20.1...v25.20.2) (2021-08-16) + + +### Bug Fixes + +* **submissions:** maintain anonymity ([0184a5f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0184a5fe3b1af635318fa0fa317e3497f24fbc90)) + +## [25.20.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.20.0...v25.20.1) (2021-08-13) + + +### Bug Fixes + +* **interval jobs:** avoid accumulation, reduce job size ([24491b4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/24491b446b870698564adb9718e868e082873539)) +* **jobs:** more general no queue same ([b1143cb](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b1143cb12bea48d75a2453f92122edcfb4fe51f1)) +* **volatile-cluster-config:** fix pathpiece instance ([dcd5ddd](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/dcd5dddec82da359a2100360cfeb6845ed320821)) + +## [25.20.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.19.3...v25.20.0) (2021-08-12) + + +### Features + +* **submission-show:** display authorship statements ([cbd6d7d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cbd6d7d2b098f8e2c921fd7a56a458d62331d784)) +* **submissions:** display authorship statements ([7749238](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7749238e554b612a8bf69e6beb94efe3e5d02973)) +* **submissions:** display submittors more explicitly ([d2e2456](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d2e2456f6204245d933fb6abc87c44388ce3e339)) + +## [25.19.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.19.2...v25.19.3) (2021-08-02) + + +### Bug Fixes + +* **submissions:** more precise feedback ([d151b6f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d151b6fc14e5b32d9f07923149923d5ab7ea4880)) + +## [25.19.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.19.1...v25.19.2) (2021-07-30) + + +### Bug Fixes + +* **jobs:** flush only partially for reliability ([59c7c17](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/59c7c1766588052383754b16e575347fa960ad6a)) +* **submissions:** allow user to resolve themself for auth'stmt' ([5bbb86a](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5bbb86aa7750dd907f49cb3ba5daf2cee8485bae)) +* **submissions:** cascade delete to authorship statements ([fcce16d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/fcce16d838e5cba3187a82a5762b831d7df54cd0)) +* **submissions:** don't leak info from corrected versions of files ([66f5e96](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/66f5e96eca4cbcb6cb092092b1b1b069ce30f159)) + +## [25.19.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.19.0...v25.19.1) (2021-07-26) + + +### Bug Fixes + +* build ([071df90](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/071df906da6c41afa226f944a90c2f294eeba243)) +* **workflows:** disabled warning for top workflows/instances ([17ed2fa](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/17ed2fad2230944c629c6a0c8d8181f6fec8983f)) + +## [25.19.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.18.2...v25.19.0) (2021-07-26) + + +### Features + +* **workflows:** replace pages with warning if turned off ([8634d20](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8634d20e2ad2d3746cf7b6111b91db9e57e4863b)) + +## [25.18.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.18.1...v25.18.2) (2021-07-21) + + +### Bug Fixes + +* **arc:** actually invalidate ([ef4734e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ef4734ebb671d9ef19c284a4c5cc9412d6e62874)) + +## [25.18.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.18.0...v25.18.1) (2021-07-21) + + +### Bug Fixes + +* typo ([26c3a60](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/26c3a60592c02570ceeed42cc977ad223baa16ae)) +* **authorship-statements:** resolve exam-part to exam properly ([3a2d031](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/3a2d031bb5f5b4d6e5df06f8ec82957a1bc81a72)) + +## [25.18.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.17.1...v25.18.0) (2021-07-21) + + +### Features + +* load shedding ([9df0686](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9df0686086ff7b64d401a2302edd2fe7636db111)) + +## [25.17.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.17.0...v25.17.1) (2021-07-21) + + +### Bug Fixes + +* build ([9fd95d1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9fd95d181c498d460eaf30436ff110f7c1f9413e)) + +## [25.17.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.16.0...v25.17.0) (2021-07-18) + + +### Features + +* demand authorship statements ([34b3e6a](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/34b3e6ae21b38a5b8389deade5deeb77b0981ead)) +* i18n form ([2d95f35](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/2d95f353c1209a4d3528c6aaf53c832bf5429a34)) +* show authorship statement requirement for sheet ([5e96982](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5e969825ad0c84c240b5c17b011dacbb63f4bfdf)) +* **exams:** basic required optional action for authorship statements ([5cc41ae](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5cc41aeef94993a24538b2f88af1fb75625036a8)) +* **exams:** disable and set use-custom field according to school setting ([22dfd33](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/22dfd33aca9b8ad797c2617bbc656cf8276edf38)) +* **exams:** display school default in form ([abd68ac](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/abd68ac0322a34afb62c416b60965e87ee6f10c2)) +* **exams:** do form validation ([bf7b25c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/bf7b25ca9e9d11df94b91f7483ee339cefd3e0c9)) +* **exams:** first do-nothing stub for exam-wide authorship statements ([0392297](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0392297ddbfccbb9a08e678696a9cedd1098121a)) +* **exams:** use template authorship statement settings if applicable ([57a259d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/57a259d8a2822ac1c593663e99f6e41163909c91)) +* **schools:** add school settings regarding authorship statements ([cb8e338](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cb8e3385889c0c4c13418bc69af091b9c8a3f22f)) +* **schools:** more school-wide configuration authorship statements ([960bd76](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/960bd76acafc9cd077b831b67a281eb7b20e703c)) +* **schools:** store school authorship statements as html ([09927ae](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/09927ae14004f7a27f816ad874704969641dad83)) +* **sheets:** add required flag and definition ([541dd76](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/541dd7688ffa36be8a968f26f920507ed5aae646)) +* **sheets:** display authship req on SShowR ([44473b4](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/44473b45756c5df20e6a81927867de191cf70366)) +* **sheets:** eliminate authship statement required Bool ([0735c05](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0735c05a7489957ed500bac1c006f4ecfdab74f3)) +* **sheets:** fetch school statement as statement default ([a39a0d7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a39a0d7c8763e158dae5750afac8a78bd953dcdf)) +* **sheets:** introduce sheet-specific statements for exam-unrelated sheets and as exam-statement overrides ([3f87f20](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/3f87f20eb14e5db8a63c61885c4570689169ebed)) + + +### Bug Fixes + +* **exams:** better behaviour for optional statements wrt school default ([fe78377](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/fe78377fae8af7766f9720628aebef599656ed2f)) +* **exams:** correctly treat school-mode optional as off by default ([ac86832](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ac86832b34a605e5d64d56ef08a871bf307347a8)) +* **exams:** fix form validation wrt non-empty statements ([0082135](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0082135c56b7fc0e5db3af6910f8365e12920c46)) +* **exams:** fixhance exam authship form section ([4109db6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/4109db6f815fbb49c861177b3caecb98c2a963d8)) +* **exams:** prefill with school authship statement in optional mode ([0cd8f4c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0cd8f4c02f383f43b5e3ea059cd3acd38595ab56)) +* **exams:** remove deprecated/unnecessary form validation wrt. authship statements ([bf059a1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/bf059a132094e53c3ef956582b5e13517e9c133d)) +* **exams:** set use-custom correctly if forced ([8bb6140](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8bb61401a77f20fcb35aa05401bf16285aad1d93)) +* **schools:** fix schools form wrt. discouraged modes ([53a8f1b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/53a8f1ba122466312947cdbdb49749a61acab37c)) +* **schools:** insert correct authorship statement definition for exam-unrelated sheets ([2272647](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/227264743e0e8d0acf76839300a034b4bb1bf2a6)) +* **schools:** perform authorship statement inserts ([579371c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/579371cffd87c247805bf4ead8bc2c278269a5ee)) +* **schools:** rename messages ([0e62073](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0e6207376043af8fe0929019e3c39f80bcfea9a6)) +* **schools:** switch authorship modes to required in form ([8fb49dd](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/8fb49dd602f4eb854b300b5b399206aa2fbca87b)) +* **schools:** use StoredMarkup instead of Html for authorship statement ([67c3016](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/67c30165ae90603e8a97ad2661d2bacb92e2e53f)) +* **sheet-show:** move message ([1d8a2ce](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/1d8a2cef60a688bd514d529f8e1230e462811f1e)) +* **sheets:** fixhance sheet authship form section ([7192cb5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7192cb527c7f66c320308a80de9906a6edc6e9ec)) + +## [25.16.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.15.3...v25.16.0) (2021-07-13) + + +### Features + +* **personalised-sheet-files:** seeds ([cf67945](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cf679452928c14200e1eb3877987ee299fbf9f6f)) + +## [25.15.3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.15.2...v25.15.3) (2021-07-08) + + +### Bug Fixes + +* avoid subSelectForeign join issues ([576fccb](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/576fccb5222a5dbd19db69f142a39b4155b7486d)) + +## [25.15.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.15.1...v25.15.2) (2021-07-06) + + +### Bug Fixes + +* **explained-selection-field:** support linebreak in titles ([627a2df](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/627a2df7adf41651e698d8cd9d632d066fc2f868)) + +## [25.15.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.15.0...v25.15.1) (2021-07-06) + + +### Bug Fixes + +* **cache:** atomicity & workflow instance invalidations ([ef7fde9](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ef7fde937ebf1bc31e3706fba1da166bb82133c5)) + +## [25.15.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.14.2...v25.15.0) (2021-07-05) + + +### Features + +* **course material:** auto vorschläge für materialtype ([decdda3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/decdda359d16cce429a7e7a07d4674840e5fe6af)) +* **course material:** first two filters ([90e4a62](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/90e4a620f0c1671ff332db1910c176e58ccbac06)) +* **course material:** materialDescription in progress ([89e9887](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/89e9887fe1112cbc21517e4b501ead33f5a969ba)) +* **course material:** materialdescription search implemented ([3a9622d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/3a9622dfb8474d9f3764f5870197e317a96d9de3)) +* **course material:** merge-request suggestions ([dc5fc3f](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/dc5fc3f710363f0644c43866505e32095b41ce92)) +* **course material:** runDB für cid nur einmal ([c09acbb](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c09acbbf8a7b95176b3d52449b3b9d26e315ccd6)) +* **course material:** small empty-bug fixed ([d8b1f97](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d8b1f9788c74ea5d7dc4f1f45432649d9601106a)) +* **workflows:** update instances from definitions ([32efdae](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/32efdae839b1a3e43ed4161d20e598964970f15e)) + + +### Bug Fixes + +* **workflows:** workflow-definition edit translations ([5c5cbad](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5c5cbaddf8b33f455ff18789806a3e0f9ac447ed)) +* typo course-assistant ([c7ce167](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c7ce1679de799285ec7a9a0a62c0a202b9078eb3)) + ## [25.14.2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.14.1...v25.14.2) (2021-06-28) diff --git a/clean.sh b/clean.sh index d63a4deab..0d7a3017b 100755 --- a/clean.sh +++ b/clean.sh @@ -24,15 +24,15 @@ if [[ "${target}" != ".stack-work" ]]; then move-back() { if [[ -d .stack-work ]]; then - mv -v .stack-work "${target}" + mv -vT .stack-work "${target}" else mkdir -v "${target}" fi - [[ -d .stack-work-clean ]] && mv -v .stack-work-clean .stack-work + [[ -d .stack-work-clean ]] && mv -vT .stack-work-clean .stack-work } - mv -v .stack-work .stack-work-clean - mv -v "${target}" .stack-work + mv -vT .stack-work .stack-work-clean + mv -vT "${target}" .stack-work trap move-back EXIT fi diff --git a/config/settings.yml b/config/settings.yml index 7cefd42f4..ff72cb3c0 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -40,7 +40,7 @@ bearer-expiration: 604800 bearer-encoding: HS256 maximum-content-length: "_env:MAX_UPLOAD_SIZE:805306368" session-files-expire: 3600 -prune-unreferenced-files-within: 57600 +prune-unreferenced-files-within: 604801 prune-unreferenced-files-interval: 3600 keep-unreferenced-files: 86400 health-check-interval: @@ -288,3 +288,5 @@ file-source-prewarm: bot-mitigations: - only-logged-in-table-sorting + +volatile-cluster-settings-cache-time: 10 diff --git a/frontend/src/app.sass b/frontend/src/app.sass index 0b3dcfd32..8665aad07 100644 --- a/frontend/src/app.sass +++ b/frontend/src/app.sass @@ -103,8 +103,8 @@ body .emph font-style: italic -a, -a:visited +a:not(.btn), +a:visited:not(.btn) text-decoration: none font-weight: 600 transition: color .2s ease, background-color .2s ease @@ -275,6 +275,9 @@ button:not(.btn-link), display: grid grid: min-content / auto-flow max-content + > form + margin: 0 !important + .buttongroup--inline display: inline-grid @@ -387,6 +390,12 @@ input[type="button"].btn-info:not(.btn-link):hover, padding-right: 10px max-width: 300px + &.table__td--unlimited + max-width: unset + + &.table__td--wide + max-width: 600px + .table__td--number width: min-content padding-left: 0 @@ -409,6 +418,12 @@ input[type="button"].btn-info:not(.btn-link):hover, line-height: 1.4 vertical-align: top + &.table__td--bottom + vertical-align: bottom + + &.table__td--middle + vertical-align: middle + .table__td--automatic font-style: oblique color: var(--color-fontsec) @@ -462,6 +477,10 @@ input[type="button"].btn-info:not(.btn-link):hover, max-height: 200px overflow-y: auto + .table__td--unlimited &, .table__td--wide & + max-height: unset + overflow-y: unset + .table--vertical th, .table__th background-color: transparent @@ -548,15 +567,14 @@ ul.list--inline .deflist__dt font-weight: 600 + font-size: 1.12em + margin-bottom: .6em .deflist__explanation color: var(--color-fontsec) - font-size: 0.9rem + font-size: 0.9em .deflist__dd - font-size: 18px - margin-bottom: 10px - > p, > .div-p margin-top: 0 @@ -573,9 +591,13 @@ ul.list--inline .deflist__dt, .deflist__dd - padding: 12px 0 + padding: .75em 0 margin: 0 - font-size: 16px + font-size: unset + + .explanation & + padding-top: 0 + padding-bottom: 0 &:last-of-type border: 0 @@ -1672,3 +1694,46 @@ video & > video object-fit: contain flex-grow: 1 + +.hr + height: 1px + width: 90% + margin: 0.5em auto + background-color: var(--color-grey) + +.authorship-statement + & > dt + font-weight: 600 + color: var(--color-fontsec) + font-style: italic + font-size: .9rem + + & > dd + margin-left: 1em + + & + dt + margin-top: .5em + +.authorship-statement-accept__accept + margin-top: 1em + display: grid + grid-template-columns: 25px 1fr + grid-template-areas: 'checkbox label' + +.authorship-statement-accept__container + max-width: 600px + max-height: 25vh + overflow: auto + +.authorship-statement-accept__accept-checkbox + align-self: center + grid-area: checkbox + +.authorship-statement-accept__accept-label + grid-area: label + font-weight: 600 + +.authorship-statement__id + font-size: .5em + font-family: var(--font-monospace) + color: var(--color-fontsec) diff --git a/frontend/src/utils/form/form.sass b/frontend/src/utils/form/form.sass index 6cc61d189..a6eac0455 100644 --- a/frontend/src/utils/form/form.sass +++ b/frontend/src/utils/form/form.sass @@ -43,7 +43,7 @@ fieldset display: grid grid-gap: 0 7px grid-template-columns: 25px 1fr - grid-template-rows: 25px 1fr + grid-template-rows: minmax(25px, auto) 1fr grid-template-areas: 'radiobox title' '. explanation' margin: 5px width: calc(33.33% - 10px) diff --git a/ghci.sh b/ghci.sh index ab5479c78..2772f30d6 100755 --- a/ghci.sh +++ b/ghci.sh @@ -16,13 +16,13 @@ unset HOST move-back() { - mv -v .stack-work .stack-work-ghci - [[ -d .stack-work-build ]] && mv -v .stack-work-build .stack-work + mv -vT .stack-work .stack-work-ghci + [[ -d .stack-work-build ]] && mv -vT .stack-work-build .stack-work } if [[ -d .stack-work-ghci ]]; then - [[ -d .stack-work ]] && mv -v .stack-work .stack-work-build - mv -v .stack-work-ghci .stack-work + [[ -d .stack-work ]] && mv -vT .stack-work .stack-work-build + mv -vT .stack-work-ghci .stack-work trap move-back EXIT fi diff --git a/haddock.sh b/haddock.sh index 00308065f..582d2381d 100755 --- a/haddock.sh +++ b/haddock.sh @@ -5,13 +5,13 @@ set -e [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || : move-back() { - mv -v .stack-work .stack-work-doc - [[ -d .stack-work-build ]] && mv -v .stack-work-build .stack-work + mv -vT .stack-work .stack-work-doc + [[ -d .stack-work-build ]] && mv -vT .stack-work-build .stack-work } if [[ -d .stack-work-doc ]]; then - [[ -d .stack-work ]] && mv -v .stack-work .stack-work-build - mv -v .stack-work-doc .stack-work + [[ -d .stack-work ]] && mv -vT .stack-work .stack-work-build + mv -vT .stack-work-doc .stack-work trap move-back EXIT fi diff --git a/hlint.sh b/hlint.sh index 5f30751cc..20acc727e 100755 --- a/hlint.sh +++ b/hlint.sh @@ -5,13 +5,13 @@ set -e [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || : move-back() { - mv -v .stack-work .stack-work-test - [[ -d .stack-work-build ]] && mv -v .stack-work-build .stack-work + mv -vT .stack-work .stack-work-test + [[ -d .stack-work-build ]] && mv -vT .stack-work-build .stack-work } if [[ -d .stack-work-test ]]; then - [[ -d .stack-work ]] && mv -v .stack-work .stack-work-build - mv -v .stack-work-test .stack-work + [[ -d .stack-work ]] && mv -vT .stack-work .stack-work-build + mv -vT .stack-work-test .stack-work trap move-back EXIT fi diff --git a/hoogle.sh b/hoogle.sh index e11f9a92e..f3bcb8bf8 100755 --- a/hoogle.sh +++ b/hoogle.sh @@ -5,13 +5,13 @@ set -e [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en .stack-work.lock "$0" "$@" || : move-back() { - mv -v .stack-work .stack-work-doc - [[ -d .stack-work-build ]] && mv -v .stack-work-build .stack-work + mv -vT .stack-work .stack-work-doc + [[ -d .stack-work-build ]] && mv -vT .stack-work-build .stack-work } if [[ -d .stack-work-doc ]]; then - [[ -d .stack-work ]] && mv -v .stack-work .stack-work-build - mv -v .stack-work-doc .stack-work + [[ -d .stack-work ]] && mv -vT .stack-work .stack-work-build + mv -vT .stack-work-doc .stack-work trap move-back EXIT fi diff --git a/messages/uniworx/categories/authorization/de-de-formal.msg b/messages/uniworx/categories/authorization/de-de-formal.msg index f54b3e7eb..e889740d7 100644 --- a/messages/uniworx/categories/authorization/de-de-formal.msg +++ b/messages/uniworx/categories/authorization/de-de-formal.msg @@ -58,6 +58,7 @@ UnauthorizedSubmissionRated: Diese Abgabe ist noch nicht korrigiert. UnauthorizedSubmissionCorrector: Sie sind nicht Korrektor:in für diese Abgabe. UnauthorizedUserSubmission: Nutzer:innen dürfen für dieses Übungsblatt keine Abgaben erstellen. UnauthorizedCorrectorSubmission: Korrektor:innen dürfen für dieses Übungsblatt keine Abgaben erstellen. +UnauthorizedCorrectionAnonymous: Korrektur ist nicht anonymisiert. DeprecatedRoute: Diese Ansicht ist obsolet und könnte in Zukunft entfallen. UnfreeMaterials: Die Materialien für diese Veranstaltung sind nicht allgemein freigegeben. UnauthorizedWrite: Sie haben hierfür keine Schreibberechtigung diff --git a/messages/uniworx/categories/authorization/en-eu.msg b/messages/uniworx/categories/authorization/en-eu.msg index 76a623a42..5d7c0cf75 100644 --- a/messages/uniworx/categories/authorization/en-eu.msg +++ b/messages/uniworx/categories/authorization/en-eu.msg @@ -58,6 +58,7 @@ UnauthorizedSubmissionRated: This submission is not yet marked. UnauthorizedSubmissionCorrector: You are no corrector for this submission. UnauthorizedUserSubmission: Users may not directly submit for this exercise sheet. UnauthorizedCorrectorSubmission: Correctors may not create submissions for this exercise sheet. +UnauthorizedCorrectionAnonymous: Correction is not anonymised. DeprecatedRoute: This view is deprecated and will be removed. UnfreeMaterials: Course material are not publicly accessable. UnauthorizedWrite: You do not have the write permission necessary to perform this action @@ -128,4 +129,4 @@ InvalidCredentialsADPasswordMustChange: Password needs to be changed InvalidCredentialsADAccountLockedOut: Account disabled by intruder detection FormFieldRequiredTip: Required fields FormFieldWorkflowDatasetTip: At least one of the marked fields must be filled -LoginTitle: Athentication \ No newline at end of file +LoginTitle: Authentication diff --git a/messages/uniworx/categories/courses/courses/de-de-formal.msg b/messages/uniworx/categories/courses/courses/de-de-formal.msg index 2334163bc..92823ea08 100644 --- a/messages/uniworx/categories/courses/courses/de-de-formal.msg +++ b/messages/uniworx/categories/courses/courses/de-de-formal.msg @@ -242,9 +242,9 @@ CourseAllApplicationsArchiveName tid@TermId ssh@SchoolId csh@CourseShorthand: #{ CourseLecInviteHeading courseName@Text: Einladung zum/zur Kursverwalter/Kursverwalterin für #{courseName} CourseLecInviteExplanation: Sie wurden eingeladen, Verwalter:in für einen Kurs zu sein. CourseUserHasPersonalisedSheetFilesFilter: Teilnehmer:in hat personalisierte Übungsblatt-Dateien für -HeadingCourseMembers: Teilnehmer +HeadingCourseMembers: Teilnehmer:innen -CourseAssistant: Assitent +CourseAssistant: Assistent:in CourseParticipantStateIsInactive: Teilnehmer:in ist nicht aktiv CourseParticipantStateIsActive: Teilnehmer:in ist aktiv CourseUserSendMail: Nachricht an Teilnehmer:in senden diff --git a/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg b/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg index 32713c799..6ce0daf8b 100644 --- a/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg +++ b/messages/uniworx/categories/courses/exam/exam/de-de-formal.msg @@ -311,4 +311,11 @@ TitleExamAutoOccurrence tid@TermId ssh@SchoolId csh@CourseShorthand examn@ExamNa ExamGradingPass: Bestanden/Nicht Bestanden ExamGradingGrades: Numerische Noten ExamGradingMixed: Gemischt -ExamFinished: Ergebnisse sichtbar ab \ No newline at end of file +ExamFinished: Ergebnisse sichtbar ab + +ExamAuthorshipStatementSection: Eigenständigkeitserklärung +ExamAuthorshipStatementRequired: Eigenständigkeitserklärung für prüfungszugehörige Übungsblattabgaben einfordern? +ExamAuthorshipStatementRequiredTip: Sollen für alle zu dieser Prüfung zugehörige Übungsblätter die Abgebenden (bei Abgabegruppen jedes Gruppenmitglied) aufgefordert werden, eine Eigenständigkeitserklärung zu akzeptieren? +ExamAuthorshipStatementRequiredForcedTip: Für dieses Institut ist vorgeschrieben, dass für alle zu diese Prüfung zugehörigen Übungsblätter die Abgebenden (bei Abgabegruppen jedes Gruppenmitglied) aufgefordert werden, eine Eigenständigkeitserklärung zu akzeptieren. +ExamAuthorshipStatementContent: Eigenständigkeitserklärung +ExamAuthorshipStatementContentForcedTip: Für dieses Institut ist die institutsweit vorgegebene Eigenständigkeitserklärung für prüfungsrelevante Übungsblätter zu verwenden. Benutzerdefinierte Erklärungen sind nicht gestattet. Für alle zu diese Prüfung zugehörigen Übungsblätter werden die Abgebenden (bei Abgabegruppen jedes Gruppenmitglied) aufgefordert, diese Eigenständigkeitserklärung zu akzeptieren. \ No newline at end of file diff --git a/messages/uniworx/categories/courses/exam/exam/en-eu.msg b/messages/uniworx/categories/courses/exam/exam/en-eu.msg index 20325610a..756491717 100644 --- a/messages/uniworx/categories/courses/exam/exam/en-eu.msg +++ b/messages/uniworx/categories/courses/exam/exam/en-eu.msg @@ -309,4 +309,11 @@ TitleExamAutoOccurrence tid ssh csh examn: #{tid} - #{ssh} - #{csh} #{examn}: Au ExamGradingPass: Passed/Failed ExamGradingGrades: Numeric grades ExamGradingMixed: Mixed -ExamFinished: Results visible from \ No newline at end of file +ExamFinished: Results visible from + +ExamAuthorshipStatementSection: Statement of Authorship +ExamAuthorshipStatementRequired: Require Statement of Authorship for exam-related exercise sheet submissions? +ExamAuthorshipStatementRequiredTip: Should submittors (each group member in case of submission groups) be required to accept a Statement of Authorship for all exercise sheets related to this exam? +ExamAuthorshipStatementRequiredForcedTip: This school enforces Statements of Authorship for all exam-related exercise sheets. +ExamAuthorshipStatementContent: Statement of Authorship +ExamAuthorshipStatementContentForcedTip: The settings of this school dictate that the school-wide Statement of Authorship for exam-related sheets must be used. Custom statements are prohibited. \ No newline at end of file diff --git a/messages/uniworx/categories/courses/material/de-de-formal.msg b/messages/uniworx/categories/courses/material/de-de-formal.msg index 63311eb1d..6e244f71c 100644 --- a/messages/uniworx/categories/courses/material/de-de-formal.msg +++ b/messages/uniworx/categories/courses/material/de-de-formal.msg @@ -30,3 +30,5 @@ MaterialVideoDownload: Herunterladen MaterialFree: Kursmaterialien ohne Anmeldung zugänglich AccessibleSince: Verfügbar seit VisibleFrom: Veröffentlicht +FilterMaterialNameSearch !ident-ok: Name +FilterMaterialTypeAndDescriptionSearch: Art/Beschreibung \ No newline at end of file diff --git a/messages/uniworx/categories/courses/material/en-eu.msg b/messages/uniworx/categories/courses/material/en-eu.msg index 8c2b0c202..4fa16fd7e 100644 --- a/messages/uniworx/categories/courses/material/en-eu.msg +++ b/messages/uniworx/categories/courses/material/en-eu.msg @@ -30,3 +30,5 @@ MaterialVideoDownload: Download MaterialFree: Course material is publicly available. AccessibleSince: Accessible since VisibleFrom: Published +FilterMaterialNameSearch !ident-ok: Name +FilterMaterialTypeAndDescriptionSearch: Type/description \ No newline at end of file diff --git a/messages/uniworx/categories/courses/sheet/de-de-formal.msg b/messages/uniworx/categories/courses/sheet/de-de-formal.msg index c6d9e7959..0a0b21fda 100644 --- a/messages/uniworx/categories/courses/sheet/de-de-formal.msg +++ b/messages/uniworx/categories/courses/sheet/de-de-formal.msg @@ -42,6 +42,8 @@ SheetPersonalisedFilesAllowNonPersonalisedSubmission: Nicht-personalisierte Abga SheetPersonalisedFilesAllowNonPersonalisedSubmissionTip: Sollen auch Kursteilnehmer:innen abgeben dürfen, für die keine personalisierten Dateien hinterlegt wurden? SheetPersonalisedFilesDownloadTemplateHere: Sie können hier ein Vorlage-Archiv für die vom System erwartete Verzeichnisstruktur für personalisierte Übungsblatt-Dateien herunterladen: SheetPersonalisedFilesUsersList: Liste von Teilnehmern mit personalisierten Übungsblatt-Dateien +SheetPersonalisedFilesMetaYAMLSeedComment: Dieser String wird in einem kryptographischen Verfahren aus Daten generiert, die Benutzer:in und Übungsblatt eindeutig identifizieren. Er ist geeignet als Seed für einen Pseudozufallsgenerator verwendet zu werden um personalisierte Dateien (teil-)zufällig zu erzeugen. +SheetPersonalisedFilesMetaYAMLNoSeedComment: Damit genügend Informationen vorhanden sind um Anhand von Daten des/der Benutzer/Benutzerin an dieser Stelle einen String zu erzeugen, der als Seed für einen Pseudozufallsgenerator geeignet ist, muss das Übungsblatt zunächst in Uni2work angelegt werden. SheetActiveFromTip: Download der Aufgabenstellung und Abgabe erst ab diesem Datum möglich. Ohne Datum keine Abgabe und keine Herausgabe der Aufgabenstellung SheetActiveToTip: Abgabe nur bis zu diesem Datum möglich. Ohne Datum unbeschränkte Abgabe möglich (soweit gefordert). SheetHintFrom: Hinweis ab @@ -150,4 +152,18 @@ SheetGradingPassPoints maxPoints@Points passingPoints@Points: Bestanden ab #{pas SheetGradingPassBinary: Bestanden/Nicht Bestanden SheetGradingPassAlways: Automatisch bestanden, sobald korrigiert - +SheetAuthorshipStatementSection: Eigenständigkeitserklärung +SheetAuthorshipStatementRequired: Eigenständigkeitserklärung für Übungsblattabgaben einfordern? +SheetAuthorshipStatementRequiredTip: Sollen die Abgebenden (bei Abgabegruppen jedes Gruppenmitglied) aufgefordert werden, eine Eigenständigkeitserklärung abzugeben? +SheetAuthorshipStatementRequiredForcedTip: Für dieses Institut sind Eigenständigkeitserklärungen für nicht-prüfungszugehörige Übungsblätter vorgeschrieben. +SheetAuthorshipStatementContent: Eigenständigkeitserklärung +SheetAuthorshipStatementContentForcedTip: Für dieses Institut ist die institutsweit vorgegebene Eigenständigkeitserklärung für nicht-prüfungszugehörige Übungsblätter zu verwenden. Benutzerdefinierte Erklärungen sind nicht gestattet. +SheetAuthorshipStatementContentOverridesExamTip: Gehört dieses Übungsblatt zu einer Prüfung mit einer prüfungsweit eingestellten Eigenständigkeitserklärung, so können Sie hier eine für dieses Übungsblatt abweichende Eigenständigkeitserklärung angeben. +SheetAuthorshipStatementExamNone: Keine Prüfung +SheetAuthorshipStatementExam: Zugeordnete Prüfung +SheetAuthorshipStatementMode: Eigenständigkeitserklärung +SheetAuthorshipStatementModeDisabled: Keine Eigenständigkeitserklärungen +SheetAuthorshipStatementModeExam: Einstellung folgt Prüfung +SheetAuthorshipStatementModeEnabled: Eigenständigkeitserklärungen fordern +SheetShowAuthorshipStatementsRequired: Eigenständigkeitserklärungen +SheetShowAuthorshipStatementsRequiredYes: Um eine Abgabe anzulegen muss eine Eigenständigkeitserklärung abgegeben werden \ No newline at end of file diff --git a/messages/uniworx/categories/courses/sheet/en-eu.msg b/messages/uniworx/categories/courses/sheet/en-eu.msg index 32292cc68..0577da6f9 100644 --- a/messages/uniworx/categories/courses/sheet/en-eu.msg +++ b/messages/uniworx/categories/courses/sheet/en-eu.msg @@ -42,6 +42,8 @@ SheetPersonalisedFilesAllowNonPersonalisedSubmission: Allow non-personalised sub SheetPersonalisedFilesAllowNonPersonalisedSubmissionTip: Should course participants with no assigned personalised files be allowed to submit anyway? SheetPersonalisedFilesDownloadTemplateHere: You can download a template for a ZIP-archive of personalised sheet files with the structure that Uni2work expects here: SheetPersonalisedFilesUsersList: List of course participants who have personalised sheet files +SheetPersonalisedFilesMetaYAMLSeedComment: This string was generated cryptographically from data uniquely identifying the user and exercise sheet. You can use it as a seed for a pseudorandom generator for generating (parts of) the personalised files. +SheetPersonalisedFilesMetaYAMLNoSeedComment: There is not enough information available to generate a seed. You will have to create the exercise sheet in Uni2work first. Once seeds can be generated they will be generated cryptographically and you may use them to generate (parts of) the personalised files. SheetActiveFromTip: The exercise sheet's assignment will only be available for download and submission starting at this time. If left empty no submission or download of assignment is ever allowed SheetActiveToTip: Submission will only be possible until this time. If left empty submissions are allowed forever (if at all possible) SheetHintFrom: Hint from @@ -149,4 +151,18 @@ SheetGradingPassPoints maxPoints passingPoints: Pass with #{passingPoints} of #{ SheetGradingPassBinary: Pass/Fail SheetGradingPassAlways: Automatically passed when corrected - +SheetAuthorshipStatementSection: Statement of Authorship +SheetAuthorshipStatementRequired: Require Statement of Authorship for submissions? +SheetAuthorshipStatementRequiredTip: Should submittors (each group member in case of submission groups) be required to accept a Statement of Authorship? +SheetAuthorshipStatementRequiredForcedTip: This school enforces Statements of Authorship for all exam-unrelated exercise sheets. +SheetAuthorshipStatementContent: Statement of Authorship +SheetAuthorshipStatementContentForcedTip: The settings of this school dictate that the school-wide Statement of Authorship for exam-unrelated sheets must be used. Custom statements are prohibited. +SheetAuthorshipStatementContentOverridesExamTip: If this exercise sheet is related to an exam with an exam-wide Statement of Authorship set, a sheet-specific adaptation can be given here. +SheetAuthorshipStatementExamNone: No Exam +SheetAuthorshipStatementExam: Related exam +SheetAuthorshipStatementMode: Statements of Authorship +SheetAuthorshipStatementModeDisabled: No Statements of Authorship +SheetAuthorshipStatementModeExam: Setting follows exam +SheetAuthorshipStatementModeEnabled: Demand Statements of Authorship +SheetShowAuthorshipStatementsRequired: Statements of Authorship +SheetShowAuthorshipStatementsRequiredYes: To submit for this exercise sheet a Statement of Authorship is required diff --git a/messages/uniworx/categories/courses/submission/de-de-formal.msg b/messages/uniworx/categories/courses/submission/de-de-formal.msg index d094355da..145768cc4 100644 --- a/messages/uniworx/categories/courses/submission/de-de-formal.msg +++ b/messages/uniworx/categories/courses/submission/de-de-formal.msg @@ -60,11 +60,15 @@ NotAParticipant email@UserEmail tid@TermId csh@CourseShorthand: #{email} ist nic TooManyParticipants: Es wurden zu viele Mitabgebende angegeben SubmissionCreated: Abgabe erfolgreich angelegt SubmissionUpdated: Abgabe erfolgreich ersetzt +SubmissionUsersUpdated: Liste von Abgebenden erfolgreich angepasst +SubmissionUnchanged: Abgabe unverändert +SubmissionUpdatedAuthorshipStatement: Eigenständigkeitserklärung erfolgreich aktualisiert FileCorrected: Korrigiert (Dateien) Corrected: Korrigiert HeadingSubmissionEditHead tid@TermId ssh@SchoolId csh@CourseShorthand sheetName@SheetName: #{tid}-#{ssh}-#{csh} #{sheetName}: Abgabe editieren/anlegen SubmissionUsers: Studenten AssignedTime: Zuteilung +SubmissionPseudonym !ident-ok: Pseudonym Pseudonyms: Pseudonyme CourseCorrectionsTitle: Korrekturen für diesen Kurs SubmissionArchiveName: abgaben @@ -192,4 +196,69 @@ SubmissionDoneByFile: Je nach Bewertungsdatei SubmissionDoneAlways: Immer SheetGroupNoGroups: Keine Gruppenabgabe -CorrDownloadVersion !ident-ok: Version \ No newline at end of file +CorrDownloadVersion !ident-ok: Version + +SubmissionAuthorshipStatement: Eigenständigkeitserklärung +SubmissionAuthorshipStatementTip: Um abgeben zu können, müssen Sie die vorgegebene Eigenständigkeitserklärung akzeptieren. Hierfür müssen Sie die Checkbox am Ende der Erklärung zu markieren. +SubmissionLecturerAuthorshipStatement: Eigenständigkeitserklärung +SubmissionLecturerAuthorshipStatementTip: Wenn Sie sich selbst als Mitabgebende/Mitabgebender eintragen müssen Sie eine Eigenständigkeitserklärung abgeben. Beachten Sie, dass Sie eine Eigenständigkeitserklärung nur für sich selbst abgeben können, nicht für etwaige andere Mitabgebende; falls Sie eine Eigenständigkeitserklärung abgeben, wird diese nur unter Ihrem Namen in Uni2work gespeichert. +SubmissionLecturerAuthorshipStatementRequiredBecauseSubmittor: Da Sie sich selbst als Mitabgebende/Mitabgebender eingetragen haben, müssen Sie eine Eigenständigkeitserklärung abgeben. +SubmissionCoSubmittorsInviteRequiredBecauseAuthorshipStatements: Da für die Abgabe zu diesem Übungsblatt die Abgabe einer Eigenständigkeitserklärung vorausgesetzt wird, werden bekannte E-Mail Adressen bekannter Benutzer nicht aufgelöst. Mitabgebende müssen stattdessen per E-Mail eingeladen werden. + +SubmissionUserTable: Abgebende +SubmissionUserDisplayName !ident-ok: Name +SubmissionUserMatriculation: Matrikelnummer +SubmissionUserEmail: E-Mail +SubmissionUserAuthorshipStatementState: Eigenständigkeitserklärung + +SubmissionAuthorshipStatementStateExists: Vorhanden +SubmissionAuthorshipStatementStateOldStatement: Unpassender Wortlaut +SubmissionAuthorshipStatementStateMissing: Fehlt + +SubmissionTitle tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName cID@CryptoFileNameSubmission !ident-ok: #{tid}-#{ssh}-#{csh} #{shn}: #{toPathPiece cID} +SubmissionHeadingEdit tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName cID@CryptoFileNameSubmission: #{tid}-#{ssh}-#{csh} #{shn}: Abgabe #{toPathPiece cID} editieren +SubmissionHeadingShow tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName cID@CryptoFileNameSubmission: #{tid}-#{ssh}-#{csh} #{shn}: Abgabe #{toPathPiece cID} +SubmissionTitleNew tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName: #{tid}-#{ssh}-#{csh} #{shn}: Abgabe anlegen +SubmissionHeadingNew tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName: #{tid}-#{ssh}-#{csh} #{shn}: Abgabe anlegen + +SubmissionAuthorshipStatementsHeading tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName cID@CryptoFileNameSubmission: #{tid}-#{ssh}-#{csh} #{shn}: Eigenständigkeitserklärungen #{toPathPiece cID} +SubmissionAuthorshipStatementsTitle tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName cID@CryptoFileNameSubmission: #{tid}-#{ssh}-#{csh} #{shn}: Eigenständigkeitserklärungen #{toPathPiece cID} + +SubmissionColumnAuthorshipStatementTime: Zeitstempel +SubmissionColumnAuthorshipStatementWording: Wortlaut +SubmissionFilterAuthorshipStatementCurrent: Aktueller Wortlaut + +SubmissionNoUsers: Diese Abgabe hat keine assoziierten Benutzer! + +CsvColumnCorrectionTerm: Semester des Kurses der Abgabe +CsvColumnCorrectionSchool: Institut des Kurses der Abgabe +CsvColumnCorrectionCourse: Kürzel des Kurses der Abgabe +CsvColumnCorrectionSheet: Name des Übungsblatts der Abgabe +CsvColumnCorrectionSubmission: Nummer der Abgabe (uwa…) +CsvColumnCorrectionSurname: Nachnamen der Abgebenden als Semikolon (;) separierte Liste +CsvColumnCorrectionFirstName: Vornamen der Abgebenden als Semikolon (;) separierte Liste +CsvColumnCorrectionName: Volle Namen der Abgebenden als Semikolon (;) separierte Liste +CsvColumnCorrectionMatriculation: Matrikelnummern der Abgebenden als Semikolon (;) separierte Liste +CsvColumnCorrectionEmail: E-Mail Adressen der Abgebenden als Semikolon (;) separierte Liste +CsvColumnCorrectionPseudonym: Abgabe-Pseudonyme der Abgebenden als Semikolon (;) separierte Liste +CsvColumnCorrectionSubmissionGroup: Feste Abgabegruppen der Abgebenden als Semikolon (;) separierte Liste +CsvColumnCorrectionAuthorshipStatementState: Zustände der Eigenständigkeitserklärungen ("#{toPathPiece ASMissing}", "#{toPathPiece ASOldStatement}" oder "#{toPathPiece ASExists}") als Semikolon (;) separierte Liste +CsvColumnCorrectionCorrectorName: Voller Name des Korrektors der Abgabe +CsvColumnCorrectionCorrectorEmail: E-Mail Adresse des Korrektors der Abgabe +CsvColumnCorrectionRatingDone: Bewertung abgeschlossen ("t"/"f") +CsvColumnCorrectionRatedAt: Zeitpunkt der Bewertung (ISO 8601) +CsvColumnCorrectionAssigned: Zeitpunkt der Zuteilung des Korrektors (ISO 8601) +CsvColumnCorrectionLastEdit: Zeitpunkt der letzten Änderung der Abgabe (ISO 8601) +CsvColumnCorrectionRatingPoints: Erreichte Punktezahl (Für “_{MsgSheetGradingPassBinary}” entspricht 0 “_{MsgRatingNotPassed}” und alles andere “_{MsgRatingPassed}”) +CsvColumnCorrectionRatingComment: Bewertungskommentar +CorrectionCsvSingleSubmittors: Eine Zeile pro Abgebende:n +CorrectionCsvSingleSubmittorsTip: Sollen Abgaben mit mehreren Abgebenden mehrfach vorkommen, sodass jeweils eine Zeile pro Abgebende:n enthalten ist, statt mehrere Abgebende in einer Zeile zusammenzufassen? + +CorrectionTableCsvNameSheetCorrections tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn}-abgaben +CorrectionTableCsvSheetNameSheetCorrections tid@TermId ssh@SchoolId csh@CourseShorthand shn@SheetName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn} Abgaben +CorrectionTableCsvNameCourseCorrections tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-abgaben +CorrectionTableCsvSheetNameCourseCorrections tid@TermId ssh@SchoolId csh@CourseShorthand: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh} Abgaben +CorrectionTableCsvNameCorrections: abgaben +CorrectionTableCsvSheetNameCorrections: Abgaben +CorrectionTableCsvNameCourseUserCorrections tid@TermId ssh@SchoolId csh@CourseShorthand displayName@Text: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName}-abgaben +CorrectionTableCsvSheetNameCourseUserCorrections tid@TermId ssh@SchoolId csh@CourseShorthand displayName@Text: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName} Abgaben \ No newline at end of file diff --git a/messages/uniworx/categories/courses/submission/en-eu.msg b/messages/uniworx/categories/courses/submission/en-eu.msg index a10d9e8de..0574c4a9d 100644 --- a/messages/uniworx/categories/courses/submission/en-eu.msg +++ b/messages/uniworx/categories/courses/submission/en-eu.msg @@ -58,11 +58,15 @@ NotAParticipant email tid csh: #{email} is not a participant of #{tid}-#{csh}. TooManyParticipants: You have specified more than the allowed number of submittors. SubmissionCreated: Successfully created submission SubmissionUpdated: Successfully replaced submission +SubmissionUsersUpdated: Successfully changed list of submittors +SubmissionUnchanged: Submission unchanged +SubmissionUpdatedAuthorshipStatement: Successfully updated Statement of Authorship FileCorrected: Marked (files) Corrected: Marked HeadingSubmissionEditHead tid ssh csh sheetName: #{tid}-#{ssh}-#{csh} #{sheetName}: Edit/Create submission SubmissionUsers: Submittors AssignedTime: Assigned +SubmissionPseudonym !ident-ok: Pseudonym Pseudonyms: Pseudonyms CourseCorrectionsTitle: Corrections for this course SubmissionArchiveName: submissions @@ -191,4 +195,69 @@ SubmissionDoneByFile: According to correction file SubmissionDoneAlways: Always SheetGroupNoGroups: No group submission -CorrDownloadVersion !ident-ok: Version \ No newline at end of file +CorrDownloadVersion !ident-ok: Version + +SubmissionAuthorshipStatement: Statement of Authorship +SubmissionAuthorshipStatementTip: To submit you have to accept the provided statement of authership. To do so you have to check the box at the end of the statement. +SubmissionLecturerAuthorshipStatement: Statement of Authorship +SubmissionLecturerAuthorshipStatementTip: If you enter yourself as a submittor you have to confirm the Statement of Authorship. Note that you can only confirm the Statement of Authorship for yourself. If you confirm it, it will be recorded only under your name. +SubmissionLecturerAuthorshipStatementRequiredBecauseSubmittor: Since you have entered yourself as a submittor you have to confirm the Statement of Authorship. +SubmissionCoSubmittorsInviteRequiredBecauseAuthorshipStatements: Since Statements of Authorship are required to submit for this exercise sheet, e-mail addresses of known users are not resolved. Instead co-submittors will have to be invited via e-mail. + +SubmissionUserTable: Submittors +SubmissionUserDisplayName: Name +SubmissionUserMatriculation: Matriculation +SubmissionUserEmail: Email +SubmissionUserAuthorshipStatementState: Statement of Authorship + +SubmissionAuthorshipStatementStateExists: Exists +SubmissionAuthorshipStatementStateOldStatement: Wrong wording +SubmissionAuthorshipStatementStateMissing: Missing + +SubmissionTitle tid ssh csh shn cID !ident-ok: #{tid}-#{ssh}-#{csh} #{shn}: #{toPathPiece cID} +SubmissionHeadingEdit tid ssh csh shn cID: #{tid}-#{ssh}-#{csh} #{shn}: Edit Submission #{toPathPiece cID} +SubmissionHeadingShow tid ssh csh shn cID: #{tid}-#{ssh}-#{csh} #{shn}: Submission #{toPathPiece cID} +SubmissionTitleNew tid ssh csh shn: #{tid}-#{ssh}-#{csh} #{shn}: Create Submission +SubmissionHeadingNew tid ssh csh shn: #{tid}-#{ssh}-#{csh} #{shn}: Create Submission + +SubmissionAuthorshipStatementsHeading tid ssh csh shn cID: #{tid}-#{ssh}-#{csh} #{shn}: Authorship Statements #{toPathPiece cID} +SubmissionAuthorshipStatementsTitle tid ssh csh shn cID: #{tid}-#{ssh}-#{csh} #{shn}: Authorship Statements #{toPathPiece cID} + +SubmissionColumnAuthorshipStatementTime: Timestamp +SubmissionColumnAuthorshipStatementWording: Wording +SubmissionFilterAuthorshipStatementCurrent: Current wording + +SubmissionNoUsers: This submission has no associated users! + +CsvColumnCorrectionTerm: Term of the course of the submission +CsvColumnCorrectionSchool: School of the course of the submission +CsvColumnCorrectionCourse: Shorthand of the course of the submission +CsvColumnCorrectionSheet: Name of the sheet of the submission +CsvColumnCorrectionSubmission: Number of the submission (uwa…) +CsvColumnCorrectionSurname: Submittor's surnames, separated by semicolon (;) +CsvColumnCorrectionFirstName: Submittor's first names, separated by semicolon (;) +CsvColumnCorrectionName: Submittor's full names, separated by semicolon (;) +CsvColumnCorrectionMatriculation: Submittor's matriculations, separated by semicolon (;) +CsvColumnCorrectionEmail: Submittor's email addresses, separated by semicolon (;) +CsvColumnCorrectionPseudonym: Submittor's submission pseudonyms, separated by semicolon (;) +CsvColumnCorrectionSubmissionGroup: Submittor's submisson groups, separated by semicolon (;) +CsvColumnCorrectionAuthorshipStatementState: States of the statements of authorship ("#{toPathPiece ASMissing}", "#{toPathPiece ASOldStatement}", or "#{toPathPiece ASExists}"), separated by semicolon (;) +CsvColumnCorrectionCorrectorName: Full name of the corrector of the submission +CsvColumnCorrectionCorrectorEmail: Email address of the corrector of the submission +CsvColumnCorrectionRatingDone: Rating done ("t"/"f") +CsvColumnCorrectionRatedAt: Timestamp of rating (ISO 8601) +CsvColumnCorrectionAssigned: Timestamp of when corrector was assigned (ISO 8601) +CsvColumnCorrectionLastEdit: Timestamp of the last edit of the submission (ISO 8601) +CsvColumnCorrectionRatingPoints: Achieved points (for “_{MsgSheetGradingPassBinary}” 0 means “_{MsgRatingNotPassed}”, everything else means “_{MsgRatingPassed}”) +CsvColumnCorrectionRatingComment: Rating comment +CorrectionCsvSingleSubmittors: One row per submittor +CorrectionCsvSingleSubmittorsTip: Should submissions with multiple submittors be split into multiple rows, such that there is one row per submittor instead of having multiple submittors within one row? + +CorrectionTableCsvNameSheetCorrections tid ssh csh shn: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn}-submissions +CorrectionTableCsvSheetNameSheetCorrections tid ssh csh shn: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldedCase shn} Submissions +CorrectionTableCsvNameCourseCorrections tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-submissions +CorrectionTableCsvSheetNameCourseCorrections tid ssh csh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh} Submissions +CorrectionTableCsvNameCorrections: submissions +CorrectionTableCsvSheetNameCorrections: Submissions +CorrectionTableCsvNameCourseUserCorrections tid ssh csh displayName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName}-submissions +CorrectionTableCsvSheetNameCourseUserCorrections tid ssh csh displayName: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-#{foldedCase csh}-#{foldCase displayName} Submissions diff --git a/messages/uniworx/categories/school/de-de-formal.msg b/messages/uniworx/categories/school/de-de-formal.msg index 28cf19c0e..66e657534 100644 --- a/messages/uniworx/categories/school/de-de-formal.msg +++ b/messages/uniworx/categories/school/de-de-formal.msg @@ -4,6 +4,8 @@ SchoolName !ident-ok: Name SchoolLdapOrganisations: Assoziierte LDAP-Fragmente SchoolLdapOrganisationsTip: Beim Login via LDAP werden dem Nutzer/der Nutzerin alle Institute zugeordnet deren assoziierte LDAP-Fragmente im Eintrag des Nutzer/der Nutzerin gefunden werden SchoolLdapOrganisationMissing: LDAP-Fragment wird benötigt + +SchoolExamSection: Prüfungen SchoolExamMinimumRegisterBeforeStart: Minimale Tage zwischen Anmeldebeginn und Termin für Prüfungen SchoolExamMinimumRegisterBeforeStartTip: Wenn angegeben werden Dozierende gezwungen Anmeldezeitraum und Prüfungstermin stets zusammen einzustellen. SchoolExamMinimumRegisterDuration: Minimale Anmeldedauer für Prüfungen @@ -12,6 +14,7 @@ SchoolExamRequireModeForRegistration: Prüfungsmodus erforderlich für Anmeldung SchoolExamRequireModeForRegistrationTip: Sollen Dozierende gezwungen werden Prüfungsmodus und Anmeldefrist stets zusammen einzustellen? SchoolExamDiscouragedModes: Prüfungsmodi mit Warnung ExamCloseMode: Prüfungs-Abschluss + SchoolUpdated ssh@SchoolId: #{ssh} erfolgreich angepasst SchoolTitle ssh@SchoolId: Institut „#{ssh}“ TitleSchoolNew: Neues Institut anlegen @@ -21,4 +24,17 @@ SchoolLecturer: Dozent:in SchoolEvaluation: Kursumfragenverwaltung SchoolExamOffice: Prüfungsverwaltung SchoolAllocation: Zentralanmeldungs-Administration -SchoolAdmin !ident-ok: Admin \ No newline at end of file +SchoolAdmin !ident-ok: Admin + +SchoolAuthorshipStatementSection: Eigenständigkeitserklärungen +SchoolAuthorshipStatementSheetMode: Modus für nicht-prüfungszugehörige Übungsblattabgaben +SchoolAuthorshipStatementSheetExamMode: Modus für prüfungszugehörige Übungsblattabgaben +SchoolAuthorshipStatementModeNone: Keine Eigenständigkeitserklärung erlauben +SchoolAuthorshipStatementModeOptional: Eigenständigkeitserklärung optional einforderbar +SchoolAuthorshipStatementModeRequired: Eigenständigkeitserklärung immer erforderlich +SchoolAuthorshipStatementSheetDefinition: Eigenständigkeitserklärung für nicht-prüfungszugehörige Übungsblattabgaben +SchoolAuthorshipStatementSheetDefinitionTip: Bitte in sowohl deutscher als auch englischer Sprache angeben. +SchoolAuthorshipStatementSheetExamDefinition: Eigenständigkeitserklärung für prüfungszugehörige Übungsblattabgaben +SchoolAuthorshipStatementSheetExamDefinitionTip: Bitte in sowohl deutscher als auch englischer Sprache angeben. +SchoolAuthorshipStatementSheetAllowOther: Abweichende Eigenständigkeitserklärungen für nicht-prüfungszugehörige Übungsblätter erlauben? +SchoolAuthorshipStatementSheetExamAllowOther: Abweichende Eigenständigkeitserklärungen für prüfungszugehörige Übungsblätter erlauben? \ No newline at end of file diff --git a/messages/uniworx/categories/school/en-eu.msg b/messages/uniworx/categories/school/en-eu.msg index c15e02e7a..31d499c65 100644 --- a/messages/uniworx/categories/school/en-eu.msg +++ b/messages/uniworx/categories/school/en-eu.msg @@ -4,6 +4,8 @@ SchoolName: Name SchoolLdapOrganisations: Associated LDAP fragments SchoolLdapOrganisationsTip: When logging in users are associated with any departments whose associated LDAP fragments are found in the users LDAP entry SchoolLdapOrganisationMissing: LDAP-fragment is required + +SchoolExamSection: Exams SchoolExamMinimumRegisterBeforeStart: Minimum number of days between start of registration period and start of exams SchoolExamMinimumRegisterBeforeStartTip: If specified course administrators will be forced to specify the start of the registration period and the start of the exam at the same time. SchoolExamMinimumRegisterDuration: Minimum duration of registration period for exams @@ -12,6 +14,7 @@ SchoolExamRequireModeForRegistration: Exam design required for registration SchoolExamRequireModeForRegistrationTip: Should course administrators be forced to fully specify their exam design when setting a registration period? SchoolExamDiscouragedModes: Exam designs to warn against ExamCloseMode: Exam closure + SchoolUpdated ssh: Successfully edited #{ssh} SchoolTitle ssh: Department „#{ssh}“ TitleSchoolNew: Create new department @@ -21,4 +24,17 @@ SchoolAdmin: Admin SchoolLecturer: Lecturer SchoolEvaluation: Course evaluation SchoolExamOffice: Exam office -SchoolAllocation: Administration of central allocations \ No newline at end of file +SchoolAllocation: Administration of central allocations + +SchoolAuthorshipStatementSection: Statements of Authorship +SchoolAuthorshipStatementSheetMode: Mode for exam-unrelated exercise sheet submissions +SchoolAuthorshipStatementSheetExamMode: Mode for exam-related exercise sheet submissions +SchoolAuthorshipStatementModeNone: No Statement of Authorship allowed +SchoolAuthorshipStatementModeOptional: Statement of Authorship optionally activatable +SchoolAuthorshipStatementModeRequired: Statement of Authorship always required +SchoolAuthorshipStatementSheetDefinition: Statement of Authorship for exam-unrelated exercise sheets +SchoolAuthorshipStatementSheetDefinitionTip: Please enter both german and english statements. +SchoolAuthorshipStatementSheetExamDefinition: Statement of Authorship for exam-related exercise sheets +SchoolAuthorshipStatementSheetExamDefinitionTip: Please enter both german and english statements. +SchoolAuthorshipStatementSheetAllowOther: Allow adaptations for exam-unrelated exercise sheets? +SchoolAuthorshipStatementSheetExamAllowOther: Allow adaptations for exam-related exercise sheets? \ No newline at end of file diff --git a/messages/uniworx/categories/settings/auth_settings/de-de-formal.msg b/messages/uniworx/categories/settings/auth_settings/de-de-formal.msg index be25a3cf8..b2d4f8036 100644 --- a/messages/uniworx/categories/settings/auth_settings/de-de-formal.msg +++ b/messages/uniworx/categories/settings/auth_settings/de-de-formal.msg @@ -42,6 +42,7 @@ AuthTagPersonalisedSheetFiles: Nutzer:in verfügt über personalisierte Übungsb AuthTagRated: Korrektur ist bewertet AuthTagUserSubmissions: Abgaben erfolgen durch Kursteilnehmer:innen AuthTagCorrectorSubmissions: Abgaben erfolgen durch Korrektor:innen +AuthTagCorrectionAnonymous: Korrektur ist anonymisiert AuthTagSelf: Nutzer:in greift nur auf eigene Daten zu AuthTagIsLDAP: Nutzer:in meldet sich mit Campus-Kennung an AuthTagIsPWHash: Nutzer:in meldet sich mit Uni2work-Kennung an diff --git a/messages/uniworx/categories/settings/auth_settings/en-eu.msg b/messages/uniworx/categories/settings/auth_settings/en-eu.msg index fdfd6c693..4465437d1 100644 --- a/messages/uniworx/categories/settings/auth_settings/en-eu.msg +++ b/messages/uniworx/categories/settings/auth_settings/en-eu.msg @@ -42,6 +42,7 @@ AuthTagPersonalisedSheetFiles: User has been assigned personalised sheet files AuthTagRated: Submission is marked AuthTagUserSubmissions: Submissions are made by course participants AuthTagCorrectorSubmissions: Submissions are registered by correctors +AuthTagCorrectionAnonymous: Correction is anonymised AuthTagSelf: User is only accessing their only data AuthTagIsLDAP: User logs in using their campus account AuthTagIsPWHash: User logs in using their Uni2work-internal account diff --git a/messages/uniworx/categories/workflows/de-de-formal.msg b/messages/uniworx/categories/workflows/de-de-formal.msg index 32456b267..6cd756c84 100644 --- a/messages/uniworx/categories/workflows/de-de-formal.msg +++ b/messages/uniworx/categories/workflows/de-de-formal.msg @@ -55,14 +55,17 @@ WorkflowDescription: Beschreibung GlobalWorkflowInstancesHeading: Workflows (Systemweit) GlobalWorkflowInstancesTitle: Workflows (Systemweit) -GlobalWorkflowInstanceInitiateHeading workflowInstanceTitle@Text: Worklow initiieren: #{workflowInstanceTitle} -GlobalWorkflowInstanceInitiateTitle: Worklow initiieren +GlobalWorkflowInstanceInitiateHeading workflowInstanceTitle@Text: Workflow initiieren: #{workflowInstanceTitle} +GlobalWorkflowInstanceInitiateTitle: Workflow initiieren SchoolWorkflowInstancesHeading ssh@SchoolId !ident-ok: Workflows (#{ssh}) SchoolWorkflowInstancesTitle ssh@SchoolId !ident-ok: Workflows (#{ssh}) -SchoolWorkflowInstanceInitiateHeading ssh@SchoolId workflowInstanceTitle@Text: Worklow initiieren: #{ssh}, #{workflowInstanceTitle} -SchoolWorkflowInstanceInitiateTitle ssh@SchoolId: Worklow initiieren: #{ssh} +SchoolWorkflowInstanceInitiateHeading ssh@SchoolId workflowInstanceTitle@Text: Workflow initiieren: #{ssh}, #{workflowInstanceTitle} +SchoolWorkflowInstanceInitiateTitle ssh@SchoolId: Workflow initiieren: #{ssh} + +WorkflowInstanceInitiateHeadingDisabled: Workflow initiieren +WorkflowInstanceInitiateTitleDisabled: Workflow initiieren WorkflowEdgeNumberedVariant edgeLabel@Text i@Natural: #{edgeLabel} (Variante #{i}) WorkflowEdgeFormEdge: Aktion @@ -120,12 +123,14 @@ GlobalWorkflowWorkflowWorkflowTitle workflowWorkflowId@CryptoFileNameWorkflowWor SchoolWorkflowWorkflowWorkflowHeading ssh@SchoolId workflowWorkflowId@CryptoFileNameWorkflowWorkflow !ident-ok: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} SchoolWorkflowWorkflowWorkflowTitle ssh@SchoolId workflowWorkflowId@CryptoFileNameWorkflowWorkflow !ident-ok: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} -WorkflowWorkflowListScopeTitle rScope@Text: Laufende Workflows - #{rScope} -WorkflowWorkflowListScopeHeading rScope@Text: Laufende Workflows (#{rScope}) +WorkflowWorkflowListScopeTitle rScope@RouteWorkflowScope: Laufende Workflows - _{rScope} +WorkflowWorkflowListScopeHeading rScope@RouteWorkflowScope: Laufende Workflows (_{rScope}) WorkflowWorkflowListInstanceTitle: Laufende Workflows für Instanz WorkflowWorkflowListInstanceHeading: Laufende Workflows für Instanz -WorkflowWorkflowListNamedInstanceTitle rScope@Text wiTitle@Text: Laufende Workflows - #{rScope}, #{wiTitle} -WorkflowWorkflowListNamedInstanceHeading rScope@Text wiTitle@Text: Laufende Workflows (#{rScope}, #{wiTitle}) +WorkflowWorkflowListNamedInstanceTitle rScope@RouteWorkflowScope wiTitle@Text: Laufende Workflows - _{rScope}, #{wiTitle} +WorkflowWorkflowListNamedInstanceHeading rScope@RouteWorkflowScope wiTitle@Text: Laufende Workflows (_{rScope}, #{wiTitle}) +WorkflowWorkflowListNamedInstanceTitleDisabled rScope@RouteWorkflowScope: Laufende Workflows - _{rScope} +WorkflowWorkflowListNamedInstanceHeadingDisabled rScope@RouteWorkflowScope: Laufende Workflows (_{rScope}) WorkflowWorkflowListTopTitle: Laufende Workflows WorkflowWorkflowListTopHeading: Laufende Workflows AdminWorkflowWorkflowListTitle: Laufende Workflows @@ -146,4 +151,13 @@ YAMLFieldDecodeFailure yamlFailure@String: Konnte YAML nicht parsen: #{yamlFailu WGFTextInput: Textfeld WGFFileUpload: Dateifeld -WorkflowWorkflowListPersons: Beteiligte Benutzer \ No newline at end of file +WorkflowWorkflowListPersons: Beteiligte Benutzer + +BtnWorkflowInstanceUpdate !ident-ok: Update +WorkflowInstanceUpdateNoActions: Keine Updates verfügbar +WorkflowInstanceUpdateUpdatedGraph: Definitions-Update erfolgreich angewandt +WorkflowInstanceUpdateUpdatedCategory: Kategorie-Update erfolgreich angewandt +WorkflowInstanceUpdateDeletedDescriptionLanguage lang@Lang: Beschreibung/Titel in Sprache „#{lang}“ gelöscht +WorkflowInstanceUpdateUpdatedDescriptionLanguage lang@Lang: Beschreibung/Titel-Update für Sprache „#{lang}“ angewandt + +WorkflowsDisabled: Workflows sind temporär deaktiviert. \ No newline at end of file diff --git a/messages/uniworx/categories/workflows/en-eu.msg b/messages/uniworx/categories/workflows/en-eu.msg index 41684ae60..2dcc37915 100644 --- a/messages/uniworx/categories/workflows/en-eu.msg +++ b/messages/uniworx/categories/workflows/en-eu.msg @@ -23,6 +23,9 @@ SchoolWorkflowInstancesTitle ssh: Workflows (#{ssh}) SchoolWorkflowInstanceInitiateHeading ssh workflowInstanceTitle: Initiate workflow: #{ssh}, #{workflowInstanceTitle} SchoolWorkflowInstanceInitiateTitle ssh: Initiate workflow: #{ssh} +WorkflowInstanceInitiateHeadingDisabled: Initiate Workflow +WorkflowInstanceInitiateTitleDisabled: Initiate Workflow + WorkflowEdgeNumberedVariant edgeLabel i: #{edgeLabel} (variant #{i}) WorkflowEdgeFormEdge: Action WorkflowEdgeFormHiddenPayload i: Hidden dataset #{i} @@ -79,12 +82,14 @@ GlobalWorkflowWorkflowWorkflowTitle workflowWorkflowId: Workflow #{toPathPiece w SchoolWorkflowWorkflowWorkflowHeading ssh workflowWorkflowId: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} SchoolWorkflowWorkflowWorkflowTitle ssh workflowWorkflowId: Workflow #{ssh}, #{toPathPiece workflowWorkflowId} -WorkflowWorkflowListScopeTitle rScope: Running workflows - #{rScope} -WorkflowWorkflowListScopeHeading rScope: Running workflows (#{rScope}) +WorkflowWorkflowListScopeTitle rScope: Running workflows - _{rScope} +WorkflowWorkflowListScopeHeading rScope: Running workflows (_{rScope}) WorkflowWorkflowListInstanceTitle: Running workflows for an instance WorkflowWorkflowListInstanceHeading: Running workflows for an instance -WorkflowWorkflowListNamedInstanceTitle rScope wiTitle: Running workflows - #{rScope}, #{wiTitle} -WorkflowWorkflowListNamedInstanceHeading rScope wiTitle: Running workflows (#{rScope}, #{wiTitle}) +WorkflowWorkflowListNamedInstanceTitle rScope wiTitle: Running workflows - _{rScope}, #{wiTitle} +WorkflowWorkflowListNamedInstanceHeading rScope wiTitle: Running workflows (_{rScope}, #{wiTitle}) +WorkflowWorkflowListNamedInstanceTitleDisabled rScope: Running Workflows - _{rScope} +WorkflowWorkflowListNamedInstanceHeadingDisabled rScope: Running Workflows (_{rScope}) WorkflowWorkflowListTopTitle: Running workflows WorkflowWorkflowListTopHeading: Running workflows AdminWorkflowWorkflowListTitle: Running workflows @@ -147,3 +152,12 @@ YAMLFieldDecodeFailure yamlFailure: Could not parse YAML: #{yamlFailure} WGFTextInput: Text field WGFFileUpload: File field WorkflowWorkflowListPersons: Involved users + +BtnWorkflowInstanceUpdate: Update +WorkflowInstanceUpdateNoActions: No updates available +WorkflowInstanceUpdateUpdatedGraph: Successfully applied updated definition +WorkflowInstanceUpdateUpdatedCategory: Successfully applied updated category +WorkflowInstanceUpdateDeletedDescriptionLanguage lang: Successfully deleted description/title for language “#{lang}” +WorkflowInstanceUpdateUpdatedDescriptionLanguage lang: Successfully applied updated description/title for language “#{lang}” + +WorkflowsDisabled: Workflows are temporarily disabled. diff --git a/messages/uniworx/utils/authorship_statement/de-de-formal.msg b/messages/uniworx/utils/authorship_statement/de-de-formal.msg new file mode 100644 index 000000000..cb8bc7829 --- /dev/null +++ b/messages/uniworx/utils/authorship_statement/de-de-formal.msg @@ -0,0 +1,2 @@ +AuthorshipStatementStatementIsRequired: Sie müssen die Eigenständigkeitserklärung als zutreffend bestätigen +AuthorshipStatementAccept: Ich habe die obenstehende Eigenständigkeitserklärung gelesen und verstanden und erkläre hiermit, dass die obenstehenden Aussagen zutreffen. \ No newline at end of file diff --git a/messages/uniworx/utils/authorship_statement/en-eu.msg b/messages/uniworx/utils/authorship_statement/en-eu.msg new file mode 100644 index 000000000..57fe51b44 --- /dev/null +++ b/messages/uniworx/utils/authorship_statement/en-eu.msg @@ -0,0 +1,2 @@ +AuthorshipStatementStatementIsRequired: You have to confirm the Statement of Authorship as true and correct +AuthorshipStatementAccept: I have read and understood the above Statement of Authorship and state that the above-mentioned statements are true and correct. \ No newline at end of file diff --git a/messages/uniworx/utils/handler_form/de-de-formal.msg b/messages/uniworx/utils/handler_form/de-de-formal.msg new file mode 100644 index 000000000..bd586dfa1 --- /dev/null +++ b/messages/uniworx/utils/handler_form/de-de-formal.msg @@ -0,0 +1,3 @@ +I18nFormNoTranslations: (Noch) keine Übersetzungen +I18nFormLanguageAlreadyExists lang@Lang: Die Sprache „#{lang}“ wurde bereits hinzugefügt. +I18nFormLanguage: Sprache \ No newline at end of file diff --git a/messages/uniworx/utils/handler_form/en-eu.msg b/messages/uniworx/utils/handler_form/en-eu.msg new file mode 100644 index 000000000..bc55d9f2b --- /dev/null +++ b/messages/uniworx/utils/handler_form/en-eu.msg @@ -0,0 +1,3 @@ +I18nFormLanguageAlreadyExists lang: Language “#{lang}” was already added. +I18nFormLanguage: Language +I18nFormNoTranslations: No translations (yet) diff --git a/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg b/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg index a38672835..c79919c59 100644 --- a/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg +++ b/messages/uniworx/utils/navigation/breadcrumbs/de-de-formal.msg @@ -96,6 +96,7 @@ BreadcrumbWorkflowInstanceWorkflowList: Laufende Workflows BreadcrumbWorkflowInstanceInitiate: Workflow starten BreadcrumbWorkflowInstanceList !ident-ok: Workflows BreadcrumbWorkflowInstanceNew: Neuer Workflow +BreadcrumbWorkflowInstanceUpdate !ident-ok: Update BreadcrumbWorkflowWorkflowList: Laufende Workflows BreadcrumbWorkflowWorkflow workflow@CryptoFileNameWorkflowWorkflow !ident-ok: #{toPathPiece workflow} BreadcrumbWorkflowWorkflowFiles: Dateien @@ -185,4 +186,5 @@ BreadcrumbCorrectionsGrade: Korrekturen eintragen BreadcrumbMessageList: Systemnachrichten BreadcrumbGlossary: Begriffsverzeichnis BreadcrumbLogin !ident-ok: Login -BreadcrumbNews: Aktuell \ No newline at end of file +BreadcrumbNews: Aktuell +BreadcrumbSubmissionAuthorshipStatements: Eigenständigkeitserklärungen \ No newline at end of file diff --git a/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg b/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg index f7fd04c97..dfb3eb21a 100644 --- a/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg +++ b/messages/uniworx/utils/navigation/breadcrumbs/en-eu.msg @@ -96,6 +96,7 @@ BreadcrumbWorkflowInstanceWorkflowList: Running workflows BreadcrumbWorkflowInstanceInitiate: Start workflow BreadcrumbWorkflowInstanceList: Workflows BreadcrumbWorkflowInstanceNew: New workflow +BreadcrumbWorkflowInstanceUpdate !ident-ok: Update BreadcrumbWorkflowWorkflowList: Running workflows BreadcrumbWorkflowWorkflow workflow: #{toPathPiece workflow} BreadcrumbWorkflowWorkflowFiles: Files @@ -186,3 +187,4 @@ BreadcrumbSheetCurrent: Current exercise sheet BreadcrumbSheetOldUnassigned: Submissions without corrector BreadcrumbLogin: Login BreadcrumbNews: News +BreadcrumbSubmissionAuthorshipStatements: Statements of Authorship diff --git a/messages/uniworx/utils/navigation/menu/de-de-formal.msg b/messages/uniworx/utils/navigation/menu/de-de-formal.msg index 383616869..69bc2b39d 100644 --- a/messages/uniworx/utils/navigation/menu/de-de-formal.msg +++ b/messages/uniworx/utils/navigation/menu/de-de-formal.msg @@ -121,6 +121,7 @@ MenuAdminWorkflowDefinitionDelete: Löschen MenuAdminWorkflowInstanceList: Workflow-Instanzen MenuAdminWorkflowInstanceNew: Neue Workflow-Instanz MenuAdminWorkflowDefinitionInstantiate: Instanziieren +MenuWorkflowInstanceUpdate !ident-ok: Update MenuWorkflowInstanceDelete: Löschen MenuWorkflowInstanceWorkflows: Laufende Workflows MenuWorkflowInstanceInitiate: Workflow starten diff --git a/messages/uniworx/utils/navigation/menu/en-eu.msg b/messages/uniworx/utils/navigation/menu/en-eu.msg index 7a02ce02a..3a4a45a16 100644 --- a/messages/uniworx/utils/navigation/menu/en-eu.msg +++ b/messages/uniworx/utils/navigation/menu/en-eu.msg @@ -122,6 +122,7 @@ MenuAdminWorkflowDefinitionDelete: Delete MenuAdminWorkflowInstanceList: Workflow instances MenuAdminWorkflowInstanceNew: New workflow instance MenuAdminWorkflowDefinitionInstantiate: Instantiate +MenuWorkflowInstanceUpdate !ident-ok: Update MenuWorkflowInstanceDelete: Delete MenuWorkflowInstanceWorkflows: Running workflows MenuWorkflowInstanceInitiate: Start workflow diff --git a/models/authorship-statements.model b/models/authorship-statements.model new file mode 100644 index 000000000..cc1cb32a1 --- /dev/null +++ b/models/authorship-statements.model @@ -0,0 +1,12 @@ +AuthorshipStatementDefinition + hash AuthorshipStatementReference + content I18nStoredMarkup + Primary hash + deriving Generic + +AuthorshipStatementSubmission + statement AuthorshipStatementDefinitionId + submission SubmissionId OnDeleteCascade OnUpdateCascade + user UserId + time UTCTime + deriving Generic diff --git a/models/config.model b/models/config.model index 202160cc7..2f91d9465 100644 --- a/models/config.model +++ b/models/config.model @@ -4,4 +4,10 @@ ClusterConfig setting ClusterSettingsKey -- I.e. Symmetric key for encrypting database-ids for use in URLs, Symmetric key for encrypting user-sessions so they can be saved directly as a browser-cookie, Symmetric key for encrypting error messages which might contain secret information, ... value Value -- JSON-encoded value Primary setting + deriving Generic + +VolatileClusterConfig + setting VolatileClusterSettingsKey + value Value + Primary setting deriving Generic \ No newline at end of file diff --git a/models/exams.model b/models/exams.model index 1c79e1f7f..e75996be3 100644 --- a/models/exams.model +++ b/models/exams.model @@ -20,6 +20,7 @@ Exam examMode ExamMode staff Text Maybe partsFrom UTCTime Maybe + authorshipStatement AuthorshipStatementDefinitionId Maybe UniqueExam course name deriving Generic ExamPart diff --git a/models/schools.model b/models/schools.model index 33975b7a3..0c96091c9 100644 --- a/models/schools.model +++ b/models/schools.model @@ -8,6 +8,12 @@ School json examRequireModeForRegistration Bool default=false examDiscouragedModes ExamModeDNF examCloseMode ExamCloseMode default='separate' + sheetAuthorshipStatementMode SchoolAuthorshipStatementMode default='optional' + sheetAuthorshipStatementDefinition AuthorshipStatementDefinitionId Maybe + sheetAuthorshipStatementAllowOther Bool default=true + sheetExamAuthorshipStatementMode SchoolAuthorshipStatementMode default='optional' + sheetExamAuthorshipStatementDefinition AuthorshipStatementDefinitionId Maybe + sheetExamAuthorshipStatementAllowOther Bool default=true 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 57213ec7b..6e650ca5a 100644 --- a/models/sheets.model +++ b/models/sheets.model @@ -15,6 +15,9 @@ Sheet -- exercise sheet for a given course anonymousCorrection Bool default=true requireExamRegistration ExamId Maybe -- Students may only submit if they are registered for the given exam allowNonPersonalisedSubmission Bool default=true + authorshipStatementMode SheetAuthorshipStatementMode default='exam' + authorshipStatementExam ExamId Maybe + authorshipStatement AuthorshipStatementDefinitionId Maybe -- sheet-specific authorship statement; for exam-unrelated sheets and as exam setting overrides CourseSheet course name deriving Generic SheetEdit -- who edited when a row in table "Course", kept indefinitely @@ -59,9 +62,9 @@ PersonalisedSheetFile deriving Eq Ord Read Show Typeable Generic FallbackPersonalisedSheetFilesKey - course CourseId OnDeleteCascade OnUpdateCascade - index Word24 - secret ByteString - generated UTCTime + course CourseId OnDeleteCascade OnUpdateCascade + index Word24 + secret ByteString + generated UTCTime UniqueFallbackPersonalisedSheetFilesKey course index - deriving Generic \ No newline at end of file + deriving Generic diff --git a/package-lock.json b/package-lock.json index 35762cf3b..f69da8c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.14.2", + "version": "25.20.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index dded55446..180c83d28 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "25.14.2", + "version": "25.20.2", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index d0306488d..a2afdc4f7 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 25.14.2 +version: 25.20.2 dependencies: - base - yesod @@ -121,6 +121,7 @@ dependencies: - http-types - jose-jwt - mono-traversable + - mono-traversable-keys - lens-aeson - systemd - streaming-commons diff --git a/routes b/routes index c9b45f88c..c7299e84c 100644 --- a/routes +++ b/routes @@ -80,6 +80,7 @@ /delete GWIDeleteR GET POST /workflows GWIWorkflowsR GET !¬empty /initiate GWIInitiateR GET POST !workflow + /update GWIUpdateR POST /global-workflows GlobalWorkflowWorkflowListR GET !free !/global-workflows/#CryptoFileNameWorkflowWorkflow GlobalWorkflowWorkflowR: / GWWWorkflowR GET POST !workflow @@ -146,6 +147,7 @@ /delete SWIDeleteR GET POST /workflows SWIWorkflowsR GET !¬empty /initiate SWIInitiateR GET POST !workflow + /update SWIUpdateR POST /workflows SchoolWorkflowWorkflowListR GET !free !/workflows/#CryptoFileNameWorkflowWorkflow SchoolWorkflowWorkflowR: / SWWWorkflowR GET POST !workflow @@ -216,6 +218,7 @@ /assign SubAssignR GET POST !lecturerANDtime /correction CorrectionR GET POST !corrector !ownerANDreadANDratedANDexam-time /invite SInviteR GET POST !ownerANDtimeANDuser-submissionsANDsubmission-groupANDexam-registeredANDpersonalised-sheet-files + /authorship-statements SubAuthorshipStatementsR GET !owner !correctorAND¬correction-anonymous !/#SubmissionFileType SubArchiveR GET !owner !corrector !/#SubmissionFileType/*FilePath SubDownloadR GET !owner !corrector /iscorrector SIsCorrR GET !corrector -- Route is used to check for corrector access to this sheet diff --git a/shell.nix b/shell.nix index 8280c7d5f..0e93c7272 100644 --- a/shell.nix +++ b/shell.nix @@ -157,7 +157,7 @@ let [[ -n "$maildev_pid" ]] && kill $maildev_pid } - ${pkgs.nodePackages.maildev}/bin/maildev --smtp $(($PORT_OFFSET + 1025)) --web $(($PORT_OFFSET + 8080)) --ip localhost --web-ip localhost &>/dev/null & + TMPDIR=''${XDG_RUNTIME_DIR} ${pkgs.nodePackages.maildev}/bin/maildev --smtp $(($PORT_OFFSET + 1025)) --web $(($PORT_OFFSET + 8080)) --ip localhost --web-ip localhost &>/dev/null & maildev_pid=$! export SMTPHOST=localhost @@ -252,8 +252,14 @@ let sleep 1 done ''; + + diffRunning = pkgs.writeScriptBin "diff-running" '' + #!${pkgs.zsh}/bin/zsh + + git diff $(cut -d '-' -f 1 <(curl -sH 'Accept: text/plain' https://uni2work.ifi.lmu.de/version)) + ''; in pkgs.mkShell { name = "uni2work"; - nativeBuildInputs = [develop inDevelop killallUni2work] ++ (with pkgs; [ nodejs-14_x postgresql_12 openldap google-chrome exiftool memcached minio minio-client gup ]) ++ (with pkgs.haskellPackages; [ stack yesod-bin hlint cabal-install weeder profiteur ]); + nativeBuildInputs = [develop inDevelop killallUni2work diffRunning] ++ (with pkgs; [ nodejs-14_x postgresql_12 openldap google-chrome exiftool memcached minio minio-client gup ]) ++ (with pkgs.haskellPackages; [ stack yesod-bin hlint cabal-install weeder profiteur ]); } diff --git a/src/Audit/Types.hs b/src/Audit/Types.hs index b5c3d1cf7..c9d118fe9 100644 --- a/src/Audit/Types.hs +++ b/src/Audit/Types.hs @@ -169,9 +169,13 @@ data Transaction } | TransactionUserAssimilated - { transactionUser :: UserId + { transactionUser , transactionAssimilatedUser :: UserId } + | TransactionUserIdentChanged + { transactionOldUserIdent + , transactionNewUserIdent :: UserIdent + } | TransactionAllocationUserEdited { transactionUser :: UserId diff --git a/src/Auth/LDAP.hs b/src/Auth/LDAP.hs index e4fee5cb2..6d6db7bce 100644 --- a/src/Auth/LDAP.hs +++ b/src/Auth/LDAP.hs @@ -204,7 +204,7 @@ campusLogin pool mode = AuthPlugin{..} searchResults <- findUser conf ldap campusIdent [ldapUserPrincipalName] case searchResults of [Ldap.SearchEntry (Ldap.Dn userDN) userAttrs] - | [principalName] <- nub $ fold [ v | (k, v) <- userAttrs, k == ldapUserPrincipalName ] + | [principalName] <- nubOrd $ fold [ v | (k, v) <- userAttrs, k == ldapUserPrincipalName ] , Right credsIdent <- Text.decodeUtf8' principalName -> handleIf isInvalidCredentials (return . Left) $ do Ldap.bind ldap (Ldap.Dn credsIdent) . Ldap.Password $ Text.encodeUtf8 campusPassword diff --git a/src/Data/Scientific/Instances.hs b/src/Data/Scientific/Instances.hs index cee91482d..87b079e7e 100644 --- a/src/Data/Scientific/Instances.hs +++ b/src/Data/Scientific/Instances.hs @@ -9,7 +9,17 @@ import Data.Scientific import Web.PathPieces +import Text.ParserCombinators.ReadP (readP_to_S) + +import Control.Monad.Fail + instance PathPiece Scientific where toPathPiece = pack . formatScientific Fixed Nothing - fromPathPiece = readFromPathPiece + + fromPathPiece = disambiguate . readP_to_S scientificP . unpack + where + disambiguate strs = case filter (\(_, rStr) -> null rStr) strs of + [(x, _)] -> pure x + _other -> fail "fromPathPiece Scientific: Ambiguous parse" + diff --git a/src/Data/Universe/TH.hs b/src/Data/Universe/TH.hs index 7ebc86ea7..0176e3a30 100644 --- a/src/Data/Universe/TH.hs +++ b/src/Data/Universe/TH.hs @@ -14,7 +14,8 @@ import Data.Universe.Helpers (interleave) import Control.Monad (unless) -import Data.List (elemIndex, nub) +import Data.List (elemIndex) +import Data.Containers.ListUtils import Control.Lens hiding (universe) import Data.Generics.Product.Types @@ -81,7 +82,7 @@ deriveUniverse' interleaveExp universeExp mkCxt tName = do usesVar ConstructorInfo{..} n | n `elem` map getTVBName constructorVars = False | otherwise = any (elemOf types n) constructorFields - fieldTypes = nub $ concatMap constructorFields datatypeCons + fieldTypes = nubOrd $ concatMap constructorFields datatypeCons iCxt' <- cxt iCxt diff --git a/src/Data/Word/Word24/Instances.hs b/src/Data/Word/Word24/Instances.hs index e1d6add1a..b80cdc620 100644 --- a/src/Data/Word/Word24/Instances.hs +++ b/src/Data/Word/Word24/Instances.hs @@ -12,6 +12,8 @@ import System.Random (Random(..)) import Data.Aeson (FromJSON(..), ToJSON(..)) import qualified Data.Aeson.Types as Aeson +import Web.PathPieces + import Data.Word.Word24 import Control.Lens @@ -19,6 +21,7 @@ import Control.Lens import Control.Monad.Fail import qualified Data.Scientific as Scientific +import Data.Scientific.Instances () import Data.Binary import Data.Bits @@ -51,6 +54,10 @@ instance FromJSON Word24 where instance ToJSON Word24 where toJSON = Aeson.Number . fromIntegral +instance PathPiece Word24 where + toPathPiece p = toPathPiece (fromIntegral p :: Word32) + fromPathPiece = Scientific.toBoundedInteger <=< fromPathPiece + -- | Big Endian instance Binary Word24 where diff --git a/src/Foundation/Authorization.hs b/src/Foundation/Authorization.hs index 5d3f9a697..042dcc374 100644 --- a/src/Foundation/Authorization.hs +++ b/src/Foundation/Authorization.hs @@ -63,6 +63,8 @@ import qualified Data.Binary as Binary import GHC.TypeLits (TypeError) import qualified GHC.TypeLits as TypeError (ErrorMessage(..)) +import Utils.VolatileClusterSettings + type BearerAuthSite site = ( MonadCrypto (HandlerFor site) @@ -464,6 +466,11 @@ maybeCurrentBearerRestrictions = runMaybeT $ do route <- MaybeT getCurrentRoute hoistMaybe $ bearer ^? _bearerRestrictionIx route +workflowsEnabledAuth :: (MonadHandler m, HandlerSite m ~ UniWorX) + => m AuthResult + -> m AuthResult +workflowsEnabledAuth = volatileBool clusterVolatileWorkflowsEnabled (unauthorizedI MsgWorkflowsDisabled) + data AuthorizationCacheKey = AuthCacheWorkflowWorkflowEdgeActors CryptoFileNameWorkflowWorkflow | AuthCacheWorkflowWorkflowViewers CryptoFileNameWorkflowWorkflow @@ -472,6 +479,7 @@ data AuthorizationCacheKey | AuthCacheSchoolFunctionList SchoolFunction | AuthCacheSystemFunctionList SystemFunction | AuthCacheLecturerList | AuthCacheExternalExamStaffList | AuthCacheCorrectorList | AuthCacheExamCorrectorList | AuthCacheTutorList | AuthCacheSubmissionGroupUserList | AuthCacheCourseRegisteredList TermId SchoolId CourseShorthand + | AuthCacheVisibleSystemMessages deriving (Eq, Ord, Read, Show, Generic, Typeable) deriving anyclass (Hashable, Binary) @@ -562,8 +570,8 @@ tagAccessPredicate AuthStudent = cacheAPSystemFunction SystemStudent (Just $ Rig | otherwise -> unauthorizedI MsgUnauthorizedStudent | otherwise -> Left $ APDB $ \_ _ mAuthId _ _ -> $cachedHereBinary mAuthId . exceptT return return $ do authId <- maybeExceptT AuthenticationRequired $ return mAuthId - isExamOffice <- lift $ exists [UserSystemFunctionUser ==. authId, UserSystemFunctionFunction ==. SystemStudent, UserSystemFunctionIsOptOut ==. False] - guardMExceptT isExamOffice $ unauthorizedI MsgUnauthorizedStudent + isStudent <- lift $ exists [UserSystemFunctionUser ==. authId, UserSystemFunctionFunction ==. SystemStudent, UserSystemFunctionIsOptOut ==. False] + guardMExceptT isStudent $ unauthorizedI MsgUnauthorizedStudent return Authorized tagAccessPredicate AuthExamOffice = cacheAPSchoolFunction SchoolExamOffice (Just $ Right diffHour) $ \mAuthId' route' _ examOfficeList -> if | maybe True (`Set.notMember` examOfficeList) mAuthId' -> Right $ case route' of @@ -786,11 +794,17 @@ tagAccessPredicate AuthCorrector = cacheAPDB (Just $ Right diffMinute) AuthCache E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val authId return Authorized where - mkCorrectorList = execWriterT $ do - tellM . fmap (setOf $ folded . _Value . _Just) . E.select . E.from $ \submission -> do + mkCorrectorList = do + submissionCorrectors <- E.select . E.from $ \submission -> E.distinctOnOrderBy [E.asc $ submission E.^. SubmissionRatingBy] $ do E.where_ . E.isJust $ submission E.^. SubmissionRatingBy return $ submission E.^. SubmissionRatingBy - tellM . fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. SheetCorrectorUser) + let submissionCorrectors' = Set.fromDistinctAscList $ mapMaybe (preview $ _Value . _Just) submissionCorrectors + + sheetCorrectors <- E.select . E.from $ \sheetCorrector -> E.distinctOnOrderBy [E.asc $ sheetCorrector E.^. SheetCorrectorUser] $ + return $ sheetCorrector E.^. SheetCorrectorUser + let sheetCorrectors' = Set.fromDistinctAscList $ map (^. _Value) sheetCorrectors + + return $ submissionCorrectors' `Set.union` sheetCorrectors' tagAccessPredicate AuthExamCorrector = cacheAPDB (Just $ Right diffMinute) AuthCacheExamCorrectorList mkExamCorrectorList $ \mAuthId' route' _ examCorrectorList -> if | maybe True (`Set.notMember` examCorrectorList) mAuthId' -> Right $ case route' of _ | is _Nothing mAuthId' -> return AuthenticationRequired @@ -1046,10 +1060,22 @@ tagAccessPredicate AuthTime = APDB $ \_ (runTACont -> cont) mAuthId route isWrit MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID - SystemMessage{systemMessageFrom, systemMessageTo} <- $cachedHereBinary smId . MaybeT $ get smId - cTime <- NTop . Just <$> liftIO getCurrentTime - guard $ NTop systemMessageFrom <= cTime - && NTop systemMessageTo >= cTime + cTime <- liftIO getCurrentTime + let cacheTime = diffDay + massageVisible = Map.fromList . map (over _1 E.unValue . over (_2 . _1) E.unValue . over (_2 . _2) E.unValue) + visibleSystemMessages <- lift . memcacheAuth' @(Map SystemMessageId (Maybe UTCTime, Maybe UTCTime)) (Right cacheTime) AuthCacheVisibleSystemMessages . fmap massageVisible . E.select . E.from $ \systemMessage -> do + E.where_ $ E.maybe E.true (E.>=. E.val cTime) (systemMessage E.^. SystemMessageTo) + E.&&. E.maybe E.false (E.<=. E.val (realToFrac diffDay `addUTCTime` cTime)) (systemMessage E.^. SystemMessageFrom) -- good enough. + return + ( systemMessage E.^. SystemMessageId + , ( systemMessage E.^. SystemMessageFrom + , systemMessage E.^. SystemMessageTo + ) + ) + (msgFrom, msgTo) <- hoistMaybe $ Map.lookup smId visibleSystemMessages + let cTime' = NTop $ Just cTime + guard $ NTop msgFrom <= cTime' + && NTop msgTo >= cTime' return Authorized MessageHideR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do @@ -1543,7 +1569,7 @@ tagAccessPredicate AuthEmpty = APDB $ \evalCtx eval' mAuthId route _ -> do orAR' = shortCircuitM (is _Authorized) (orAR mr) _andAR' = shortCircuitM (is _Unauthorized) (andAR mr) - workflowInstanceWorkflowsEmpty rScope win = selectLanguageI18n <=< $cachedHereBinary (evalCtx, mAuthId, route) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowWorkflowsNotEmpty) $ do + workflowInstanceWorkflowsEmpty rScope win = workflowsEnabledAuth $ selectLanguageI18n <=< $cachedHereBinary (evalCtx, mAuthId, route) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowWorkflowsNotEmpty) $ do roles <- memcacheAuth' (Right diffDay) (AuthCacheWorkflowInstanceWorkflowViewers win rScope) $ do scope <- fromRouteWorkflowScope rScope let dbScope = scope ^. _DBWorkflowScope @@ -1649,6 +1675,13 @@ tagAccessPredicate AuthCorrectorSubmissions = APDB $ \_ _ _ route _ -> case rout guard submissionModeCorrector return Authorized r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r +tagAccessPredicate AuthCorrectionAnonymous = APDB $ \_ _ _ route _ -> case route of + CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedCorrectionAnonymous) $ do + Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh + Entity _ Sheet{ sheetAnonymousCorrection } <- $cachedHereBinary (cid, shn) . MaybeT . getBy $ CourseSheet cid shn + guard sheetAnonymousCorrection + return Authorized + r -> $unsupportedAuthPredicate AuthCorrectionAnonymous r tagAccessPredicate AuthSelf = APDB $ \_ _ mAuthId route _ -> exceptT return return $ do referencedUser' <- case route of AdminUserR cID -> return $ Left cID @@ -1712,7 +1745,7 @@ tagAccessPredicate AuthAuthentication = APDB $ \_ _ mAuthId route _ -> case rout guard $ not systemMessageAuthenticatedOnly || isAuthenticated return Authorized r -> $unsupportedAuthPredicate AuthAuthentication r -tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> do +tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> workflowsEnabledAuth $ do mr <- getMsgRenderer let orAR', _andAR' :: forall m'. Monad m' => m' AuthResult -> m' AuthResult -> m' AuthResult orAR' = shortCircuitM (is _Authorized) (orAR mr) @@ -1720,7 +1753,7 @@ tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> wInitiate win rScope = selectLanguageI18n <=< $memcacheAuthHere' (Right diffDay) (evalCtx, route, mAuthId) . maybeT (unauthorizedI18n MsgUnauthorizedWorkflowInitiate) $ do -- @isWrite@ not included since it should make no difference regarding initiation (in the end that will always be a write) - roles <- memcacheAuth' (Right diffDay) (AuthCacheWorkflowInstanceInitiators win rScope) $ do + roles <- memcacheAuth' @(Set (WorkflowRole UserId)) (Right diffDay) (AuthCacheWorkflowInstanceInitiators win rScope) $ do scope <- MaybeT . $cachedHereBinary rScope . runMaybeT $ fromRouteWorkflowScope rScope Entity _ WorkflowInstance{..} <- $cachedHereBinary (win, scope) . MaybeT . getBy . UniqueWorkflowInstance win $ scope ^. _DBWorkflowScope wiGraph <- lift $ getSharedIdWorkflowGraph workflowInstanceGraph @@ -1753,7 +1786,7 @@ tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> guardM . fmap (is _Authorized) $ ofoldl1' orAR' . mapNonNull evalRole =<< hoistMaybe (fromNullable $ otoList edges) return Authorized | otherwise = flip orAR' (wWorkflow True cID) . maybeT (unauthorizedI MsgUnauthorizedWorkflowRead) $ do - (wwId, roles) <- memcacheAuth' (Right diffDay) (AuthCacheWorkflowWorkflowViewers cID) $ do + (wwId, roles) <- memcacheAuth' @(WorkflowWorkflowId, Set (WorkflowRole UserId)) (Right diffDay) (AuthCacheWorkflowWorkflowViewers cID) $ do wwId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID WorkflowWorkflow{..} <- MaybeT . $cachedHereBinary wwId $ get wwId wwGraph <- lift $ getSharedIdWorkflowGraph workflowWorkflowGraph @@ -1772,7 +1805,7 @@ tagAccessPredicate AuthWorkflow = APDB $ \evalCtx eval' mAuthId route isWrite -> guard $ Map.lookup payload (workflowStateCurrentPayloads prevActs) /= Map.lookup payload (wpPayload act) fmap (toNullable . wpvViewers) . hoistMaybe $ Map.lookup payload . wgnPayloadView =<< Map.lookup (wpTo prevAct) (wgNodes wwGraph) - return (wwId, fold nodeViewers <> fold payloadViewers :: (Set (WorkflowRole UserId))) + return (wwId, fold nodeViewers <> fold payloadViewers) let evalRole role = lift . evalWriterT $ evalWorkflowRoleFor' eval' mAuthId (Just wwId) role route isWrite @@ -1837,6 +1870,28 @@ routeAuthTags = fmap predDNFEntail . ofoldM parse defaultAuthDNF . routeAttrs Just t' -> Right . predDNFOr prev . PredDNF $ Set.singleton t' Nothing -> Left $ InvalidAuthTag t +broadenRoute :: AuthTag -> Route UniWorX -> Route UniWorX +broadenRoute aTag route = case (aTag, route) of + (AuthAdmin, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR + (AuthAdmin, AllocationR tid ssh ash _) -> AllocationR tid ssh ash AShowR + (AuthAdmin, SchoolR ssh _) -> SchoolR ssh SchoolEditR + (AuthAdmin, _) -> NewsR + + (AuthStudent, _) -> NewsR + + (AuthExamOffice, CExamR tid ssh csh examn _) -> CExamR tid ssh csh examn EShowR + (AuthExamOffice, EExamR tid ssh coursen examn _) -> EExamR tid ssh coursen examn EEShowR + (AuthExamOffice, CourseR _ ssh _ _) -> SchoolR ssh SchoolEditR + (AuthExamOffice, SchoolR ssh _) -> SchoolR ssh SchoolEditR + (AuthExamOffice, _) -> NewsR + + (AuthLecturer, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR + (AuthLecturer, AllocationR tid ssh ash _) -> AllocationR tid ssh ash AShowR + (AuthLecturer, EExamR tid ssh coursen examn _) -> EExamR tid ssh coursen examn EEShowR + (AuthLecturer, _) -> NewsR + + _other -> route + evalAuthTags :: forall ctx m. (HasCallStack, Binary ctx, MonadAP m) => ctx -> AuthTagActive -> (forall m'. MonadAP m' => AuthTagsEval m') -> AuthTagsEval m -- ^ `tell`s disabled predicates, identified as pivots evalAuthTags ctx authActive@AuthTagActive{..} cont (map (Set.toList . toNullable) . Set.toList . dnfTerms -> authDNF') mAuthId route isWrite @@ -1850,8 +1905,9 @@ evalAuthTags ctx authActive@AuthTagActive{..} cont (map (Set.toList . toNullable authTagIsInactive = not . authTagIsActive evalAuthTag :: AuthTag -> WriterT (Set AuthTag) m AuthResult - evalAuthTag authTag = lift . ($runCachedMemoT :: CachedMemoT (ctx, AuthTag, Maybe UserId, Route UniWorX, Bool) AuthResult m _ -> m _) $ for5 memo (const evalAccessPred') ctx authTag mAuthId route isWrite + evalAuthTag authTag = lift . ($runCachedMemoT :: CachedMemoT (ctx, AuthTag, Maybe UserId, Route UniWorX, Bool) AuthResult m _ -> m _) $ for5 memo (const evalAccessPred') ctx authTag mAuthId route'' isWrite where + route'' = broadenRoute authTag route evalAccessPred' authTag' mAuthId' route' isWrite' = lift $ do $logDebugS "evalAccessPred" $ tshow (authTag', mAuthId', route', isWrite') observeAuthTagEvaluation authTag' (classifyHandler route') $ do @@ -1979,7 +2035,7 @@ evalWorkflowRoleFor' :: forall m backend. -> Route UniWorX -> Bool -> WriterT (Set AuthTag) (ReaderT backend m) AuthResult -evalWorkflowRoleFor' eval mAuthId mwwId wRole route isWrite = do +evalWorkflowRoleFor' eval mAuthId mwwId wRole route isWrite = workflowsEnabledAuth $ do mr <- getMsgRenderer let @@ -2028,7 +2084,7 @@ evalWorkflowRoleFor :: ( HasCallStack -> Route UniWorX -> Bool -> ReaderT backend m AuthResult -evalWorkflowRoleFor mAuthId mwwId wRole route isWrite = do +evalWorkflowRoleFor mAuthId mwwId wRole route isWrite = workflowsEnabledAuth $ do isSelf <- (== mAuthId) <$> liftHandler defaultMaybeAuthId tagActive <- if | isSelf -> fromMaybe def <$> lookupSessionJson SessionActiveAuthTags @@ -2052,7 +2108,7 @@ hasWorkflowRole :: ( HasCallStack -> Route UniWorX -> Bool -> ReaderT backend m AuthResult -hasWorkflowRole mwwId wRole route isWrite = do +hasWorkflowRole mwwId wRole route isWrite = workflowsEnabledAuth $ do mAuthId <- maybeAuthId evalWorkflowRoleFor mAuthId mwwId wRole route isWrite @@ -2070,7 +2126,7 @@ mayViewWorkflowAction' :: forall backend m fileid. -> WorkflowWorkflowId -> WorkflowAction fileid UserId -> WriterT (Set AuthTag) (ReaderT backend m) Bool -mayViewWorkflowAction' eval mAuthId wwId WorkflowAction{..} = hoist (withReaderT $ projectBackend @SqlReadBackend) . maybeT (return False) $ do +mayViewWorkflowAction' eval mAuthId wwId WorkflowAction{..} = volatileBool clusterVolatileWorkflowsEnabled (return False) . hoist (withReaderT $ projectBackend @SqlReadBackend) . maybeT (return False) $ do Entity _ WorkflowWorkflow{..} <- MaybeT . lift $ getWorkflowWorkflowState wwId rScope <- hoist lift . toRouteWorkflowScope $ _DBWorkflowScope # workflowWorkflowScope cID <- catchMaybeT (Proxy @CryptoIDError) . lift . lift $ encrypt wwId @@ -2100,7 +2156,7 @@ mayViewWorkflowAction :: forall backend m fileid. -> WorkflowWorkflowId -> WorkflowAction fileid UserId -> ReaderT backend m Bool -mayViewWorkflowAction mAuthId wwId act = do +mayViewWorkflowAction mAuthId wwId act = volatileBool clusterVolatileWorkflowsEnabled (return False) $ do isSelf <- (== mAuthId) <$> liftHandler defaultMaybeAuthId tagActive <- if | isSelf -> fromMaybe def <$> lookupSessionJson SessionActiveAuthTags diff --git a/src/Foundation/DB.hs b/src/Foundation/DB.hs index 87f93a952..63993f607 100644 --- a/src/Foundation/DB.hs +++ b/src/Foundation/DB.hs @@ -2,6 +2,7 @@ module Foundation.DB ( runDBRead, runDBRead' , runSqlPoolRetry, runSqlPoolRetry' , dbPoolPressured + , runDBInternal, runDBInternal' ) where import Import.NoFoundation hiding (runDB, getDBRunner) @@ -62,6 +63,15 @@ runDBRead' :: CallStack -> ReaderT SqlReadBackend (HandlerFor UniWorX) a -> (Han runDBRead' lbl action = do $logDebugS "YesodPersist" "runDBRead" flip (runSqlPoolRetry' . withReaderT SqlReadBackend $ [executeQQ|SET TRANSACTION READ ONLY|] *> action) lbl . appConnPool =<< getYesod + +runDBInternal :: HasCallStack + => ReaderT SqlBackend (HandlerFor UniWorX) a -> HandlerFor UniWorX a +runDBInternal = runDBInternal' callStack + +runDBInternal' :: CallStack -> ReaderT SqlBackend (HandlerFor UniWorX) a -> HandlerFor UniWorX a +runDBInternal' lbl action = do + $logDebugS "YesodPersist" "runDBInternal" + flip (runSqlPoolRetry' action) lbl . appConnPool =<< getYesod dbPoolPressured :: ( MonadHandler m , HandlerSite m ~ UniWorX diff --git a/src/Foundation/I18n.hs b/src/Foundation/I18n.hs index b720355c6..f85cc309a 100644 --- a/src/Foundation/I18n.hs +++ b/src/Foundation/I18n.hs @@ -14,7 +14,8 @@ module Foundation.I18n , UniWorXMetricsMessage(..), UniWorXNewsMessage(..), UniWorXSchoolMessage(..), UniWorXSystemMessageMessage(..) , UniWorXTermMessage(..), UniWorXSendMessage(..), UniWorXSiteLayoutMessage(..), UniWorXErrorMessage(..) , UniWorXI18nMessage(..),UniWorXJobsHandlerMessage(..), UniWorXModelTypesMessage(..), UniWorXYesodMiddlewareMessage(..) - , ShortTermIdentifier(..) + , UniWorXAuthorshipStatementMessage(..) + , ShortTermIdentifier(..) , MsgLanguage(..) , ShortSex(..) , ShortWeekDay(..) @@ -190,6 +191,7 @@ mkMessageAddition ''UniWorX "TablePagination" "messages/uniworx/utils/table_pagi mkMessageAddition ''UniWorX "Util" "messages/uniworx/utils/utils" "de-de-formal" mkMessageAddition ''UniWorX "Rating" "messages/uniworx/utils/rating" "de-de-formal" mkMessageAddition ''UniWorX "SiteLayout" "messages/uniworx/utils/site_layout" "de-de-formal" +mkMessageAddition ''UniWorX "AuthorshipStatement" "messages/uniworx/utils/authorship_statement" "de-de-formal" mkMessageVariant ''UniWorX ''CampusMessage "messages/auth/campus" "de" mkMessageVariant ''UniWorX ''DummyMessage "messages/auth/dummy" "de" mkMessageVariant ''UniWorX ''PWHashMessage "messages/auth/pw-hash" "de" @@ -232,17 +234,19 @@ instance RenderMessage UniWorX Load where Load { byTutorial = Just True , byProportion = p } -> MsgCorByProportionIncludingTutorial p Load { byTutorial = Just False, byProportion = p } -> MsgCorByProportionExcludingTutorial p -newtype MsgLanguage = MsgLanguage Lang +data MsgLanguage = MsgLanguage { unMsgLanguage :: Lang } | MsgLanguageEndonym { unMsgLanguage :: Lang } deriving stock (Eq, Ord, Show, Read) instance RenderMessage UniWorX MsgLanguage where - renderMessage foundation ls (MsgLanguage lang@(map mk . Text.splitOn "-" -> lang')) + renderMessage foundation ls msg@(unMsgLanguage -> lang@(map mk . Text.splitOn "-" -> lang')) | ("de" : "DE" : _) <- lang' = mr MsgGermanGermany | ("de" : _) <- lang' = mr MsgGerman | ("en" : "EU" : _) <- lang' = mr MsgEnglishEurope | ("en" : _) <- lang' = mr MsgEnglish | otherwise = lang where - mr = renderMessage foundation $ lang : filter (/= lang) ls + mr = renderMessage foundation $ case msg of + MsgLanguageEndonym _ -> lang : filter (/= lang) ls + MsgLanguage _ -> ls appLanguagesOpts :: ( MonadHandler m , RenderMessage (HandlerSite m) MsgLanguage @@ -301,6 +305,11 @@ embedRenderMessage ''UniWorX ''UrlFieldMessage id embedRenderMessageVariant ''UniWorX ''ADInvalidCredentials ("InvalidCredentials" <>) +embedRenderMessage ''UniWorX ''SchoolAuthorshipStatementMode id +embedRenderMessage ''UniWorX ''SheetAuthorshipStatementMode id + +embedRenderMessage ''UniWorX ''AuthorshipStatementSubmissionState $ concat . ("SubmissionAuthorshipStatementState" :) . drop 1 . splitCamel + newtype ShortSex = ShortSex Sex embedRenderMessageVariant ''UniWorX ''ShortSex ("Short" <>) @@ -502,18 +511,18 @@ instance RenderMessage UniWorX RouteWorkflowScope where mr = renderMessage foundation ls -unRenderMessage' :: (Eq a, Finite a, RenderMessage master a) => (Text -> Text -> Bool) -> master -> Text -> [a] -unRenderMessage' cmp foundation inp = nub $ do +unRenderMessage' :: (Ord a, Finite a, RenderMessage master a) => (Text -> Text -> Bool) -> master -> Text -> [a] +unRenderMessage' cmp foundation inp = nubOrd $ do l <- appLanguages' x <- universeF guard $ renderMessage foundation (l : filter (/= l) appLanguages') x `cmp` inp return x where appLanguages' = toList appLanguages -unRenderMessage :: (Eq a, Finite a, RenderMessage master a) => master -> Text -> [a] +unRenderMessage :: (Ord a, Finite a, RenderMessage master a) => master -> Text -> [a] unRenderMessage = unRenderMessage' (==) -unRenderMessageLenient :: forall a master. (Eq a, Finite a, RenderMessage master a) => master -> Text -> [a] +unRenderMessageLenient :: forall a master. (Ord a, Finite a, RenderMessage master a) => master -> Text -> [a] unRenderMessageLenient = unRenderMessage' cmp where cmp = (==) `on` mk . under packed (filter Char.isAlphaNum . concatMap unidecode) diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index 5be9cbd42..28303797b 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -47,6 +47,8 @@ import qualified Data.Set as Set import Data.List (inits) +import Utils.VolatileClusterSettings + type Breadcrumb = (Text, Maybe (Route UniWorX)) @@ -141,6 +143,7 @@ breadcrumb (SchoolR ssh sRoute) = case sRoute of i18nCrumb MsgBreadcrumbWorkflowInstanceInitiate . Just . SchoolR ssh $ if | mayEdit -> SchoolWorkflowInstanceR win SWIEditR | otherwise -> SchoolWorkflowInstanceListR + SWIUpdateR -> i18nCrumb MsgBreadcrumbWorkflowInstanceUpdate . Just . SchoolR ssh $ SchoolWorkflowInstanceR win SWIEditR SchoolWorkflowWorkflowListR -> i18nCrumb MsgBreadcrumbWorkflowWorkflowList . Just $ SchoolR ssh SchoolWorkflowInstanceListR SchoolWorkflowWorkflowR cID sRoute' -> case sRoute' of SWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) . Just $ SchoolR ssh SchoolWorkflowWorkflowListR @@ -322,17 +325,16 @@ breadcrumb (CourseR tid ssh csh (SheetR shn sRoute)) = case sRoute of SubmissionR cid sRoute' -> case sRoute' of SubShowR -> useRunDB $ do mayList <- hasReadAccessTo $ CSheetR tid ssh csh shn SSubsR - if - | mayList - -> i18nCrumb MsgBreadcrumbSubmission . Just $ CSheetR tid ssh csh shn SSubsR - | otherwise - -> i18nCrumb MsgBreadcrumbSubmission . Just $ CSheetR tid ssh csh shn SShowR + return ( toPathPiece cid + , Just . CSheetR tid ssh csh shn $ bool SShowR SSubsR mayList + ) CorrectionR -> i18nCrumb MsgMenuCorrection . Just $ CSubmissionR tid ssh csh shn cid SubShowR SubDelR -> i18nCrumb MsgMenuSubmissionDelete . Just $ CSubmissionR tid ssh csh shn cid SubShowR SubAssignR -> i18nCrumb MsgCorrectorAssignTitle . Just $ CSubmissionR tid ssh csh shn cid SubShowR SInviteR -> i18nCrumb MsgBreadcrumbSubmissionUserInvite . Just $ CSubmissionR tid ssh csh shn cid SubShowR SubArchiveR sft -> i18nCrumb sft . Just $ CSubmissionR tid ssh csh shn cid SubShowR SubDownloadR _ _ -> i18nCrumb MsgBreadcrumbSubmissionFile . Just $ CSubmissionR tid ssh csh shn cid SubShowR + SubAuthorshipStatementsR -> i18nCrumb MsgBreadcrumbSubmissionAuthorshipStatements . Just $ CSubmissionR tid ssh csh shn cid SubShowR SArchiveR -> i18nCrumb MsgBreadcrumbSheetArchive . Just $ CSheetR tid ssh csh shn SShowR SIsCorrR -> i18nCrumb MsgBreadcrumbSheetIsCorrector . Just $ CSheetR tid ssh csh shn SShowR SPseudonymR -> i18nCrumb MsgBreadcrumbSheetPseudonym . Just $ CSheetR tid ssh csh shn SShowR @@ -428,6 +430,7 @@ breadcrumb (GlobalWorkflowInstanceR win sRoute) = case sRoute of i18nCrumb MsgBreadcrumbWorkflowInstanceInitiate . Just $ if | mayEdit -> GlobalWorkflowInstanceR win GWIEditR | otherwise -> GlobalWorkflowInstanceListR + GWIUpdateR -> i18nCrumb MsgBreadcrumbWorkflowInstanceUpdate . Just $ GlobalWorkflowInstanceR win GWIEditR breadcrumb GlobalWorkflowWorkflowListR = i18nCrumb MsgBreadcrumbWorkflowWorkflowList $ Just GlobalWorkflowInstanceListR breadcrumb (GlobalWorkflowWorkflowR cID sRoute) = case sRoute of GWWWorkflowR -> i18nCrumb (MsgBreadcrumbWorkflowWorkflow cID) $ Just GlobalWorkflowWorkflowListR @@ -617,7 +620,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the activeLang <- selectLanguage appLanguages let navChildren = flip map (toList appLanguages) $ \lang -> NavLink - { navLabel = MsgLanguage lang + { navLabel = MsgLanguageEndonym lang , navRoute = (LangR, [(toPathPiece GetReferer, toPathPiece currentRoute) | currentRoute <- hoistMaybe mCurrentRoute ]) , navAccess' = NavAccessTrue , navType = NavTypeButton @@ -756,6 +759,8 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the } } , do + guardVolatile clusterVolatileWorkflowsEnabled + authCtx <- getAuthContext (haveInstances, haveWorkflows) <- lift . memcachedBy (Just . Right $ 2 * diffMinute) (NavCacheHaveTopWorkflowsInstances authCtx) . useRunDB $ (,) <$> haveTopWorkflowInstances @@ -2732,7 +2737,7 @@ haveTopWorkflowInstances, haveTopWorkflowWorkflows ) => ReaderT backend m Bool haveTopWorkflowInstances = hoist liftHandler . withReaderT (projectBackend @SqlReadBackend) . $cachedHere . maybeT (return False) $ do - roles <- memcachedBy (Just $ Right diffDay) NavCacheHaveTopWorkflowInstancesRoles $ do + roles <- memcachedBy @(Set ((RouteWorkflowScope, WorkflowInstanceName), WorkflowRole UserId)) (Just $ Right diffDay) NavCacheHaveTopWorkflowInstancesRoles $ do let getInstances = E.selectSource . E.from $ \workflowInstance -> do E.where_ . isTopWorkflowScopeSql $ workflowInstance E.^. WorkflowInstanceScope diff --git a/src/Foundation/SiteLayout.hs b/src/Foundation/SiteLayout.hs index b8e1751c5..ebec84d65 100644 --- a/src/Foundation/SiteLayout.hs +++ b/src/Foundation/SiteLayout.hs @@ -39,6 +39,9 @@ import Text.Cassius (cassiusFile) import Text.Hamlet (hamletFile) import Data.FileEmbed (embedFile) +import Utils.VolatileClusterSettings + + data CourseFavouriteToggleButton = BtnCourseFavouriteToggleManual | BtnCourseFavouriteToggleAutomatic @@ -303,7 +306,7 @@ siteLayout' overrideHeading widget = do let cK = MemcachedKeyFavouriteQuickActions (tid, ssh, csh) ctx langs $logDebugS "FavouriteQuickActions" $ tshow cK <> " Checking..." poolIsPressured <- dbPoolPressured - items <- if + items <- volatileBool clusterVolatileQuickActionsEnabled (return Nothing) $ if | poolIsPressured -> Nothing <$ observeFavouritesSkippedDueToDBLoad | otherwise -> memcachedLimitedKeyTimeoutBy MemcachedLimitKeyFavourites appFavouritesQuickActionsBurstsize appFavouritesQuickActionsAvgInverseRate 1 diff --git a/src/Foundation/Type.hs b/src/Foundation/Type.hs index 3b7494d3c..8a4a38c23 100644 --- a/src/Foundation/Type.hs +++ b/src/Foundation/Type.hs @@ -10,7 +10,7 @@ module Foundation.Type , AppMemcachedLocal(..) , _memcachedLocalARC , SMTPPool - , _appSettings', _appStatic, _appConnPool, _appSmtpPool, _appLdapPool, _appWidgetMemcached, _appHttpManager, _appLogger, _appLogSettings, _appCryptoIDKey, _appClusterID, _appInstanceID, _appJobState, _appSessionStore, _appSecretBoxKey, _appJSONWebKeySet, _appHealthReport, _appMemcached, _appUploadCache, _appVerpSecret, _appAuthKey + , _appSettings', _appStatic, _appConnPool, _appSmtpPool, _appLdapPool, _appWidgetMemcached, _appHttpManager, _appLogger, _appLogSettings, _appCryptoIDKey, _appClusterID, _appInstanceID, _appJobState, _appSessionStore, _appSecretBoxKey, _appJSONWebKeySet, _appHealthReport, _appMemcached, _appUploadCache, _appVerpSecret, _appAuthKey, _appPersonalisedSheetFilesSeedKey, _appVolatileClusterSettingsCache , DB, Form, MsgRenderer, MailM, DBFile ) where @@ -37,6 +37,7 @@ import Utils.Metrics (DBConnUseState) import qualified Data.ByteString.Lazy as Lazy import Data.Time.Clock.POSIX (POSIXTime) import GHC.Fingerprint (Fingerprint) +import Handler.Sheet.PersonalisedFiles.Types (PersonalisedSheetFilesSeedKey) type SMTPPool = Pool SMTPConnection @@ -93,6 +94,8 @@ data UniWorX = UniWorX , appFileSourceARC :: Maybe (ARCHandle (FileContentChunkReference, (Int, Int)) Int ByteString) , appFileSourcePrewarm :: Maybe (LRUHandle (FileContentChunkReference, (Int, Int)) UTCTime Int ByteString) , appFileInjectInhibit :: TVar (IntervalMap UTCTime (Set FileContentReference)) + , appPersonalisedSheetFilesSeedKey :: PersonalisedSheetFilesSeedKey + , appVolatileClusterSettingsCache :: TVar VolatileClusterSettingsCache } deriving (Typeable) makeLenses_ ''UniWorX diff --git a/src/Foundation/Yesod/Auth.hs b/src/Foundation/Yesod/Auth.hs index 8460462e9..29c77c654 100644 --- a/src/Foundation/Yesod/Auth.hs +++ b/src/Foundation/Yesod/Auth.hs @@ -296,7 +296,7 @@ upsertCampusUser upsertMode ldapData = do Right str <- return $ Text.decodeUtf8' v' return str - termNames = nubBy ((==) `on` CI.mk) $ do + termNames = nubOrdOn CI.mk $ do (k, v) <- ldapData guard $ k == ldapUserFieldName v' <- v @@ -505,7 +505,7 @@ updateUserLanguage (Just lang) = do muid <- maybeAuthId for_ muid $ \uid -> do langs <- languages - update uid [ UserLanguages =. Just (Languages $ lang : nub (filter ((&&) <$> (`elem` appLanguages) <*> (/= lang)) langs)) ] + update uid [ UserLanguages =. Just (Languages $ lang : nubOrd (filter ((&&) <$> (`elem` appLanguages) <*> (/= lang)) langs)) ] setRegisteredCookie CookieLang lang return $ Just lang updateUserLanguage Nothing = runMaybeT $ do diff --git a/src/Handler/Admin/Test.hs b/src/Handler/Admin/Test.hs index 2de4ec9f2..ac62ab491 100644 --- a/src/Handler/Admin/Test.hs +++ b/src/Handler/Admin/Test.hs @@ -149,15 +149,16 @@ postAdminTestR = do -- This /needs/ to replace all occurrences of @mreq@ with @mpreq@ (no fields should be /actually/ required) mkAddForm :: ListPosition -- ^ Approximate position of the add-widget -> Natural -- ^ Dimension Index, outermost dimension ist 0 i.e. if dimension is 3 hyperplane-adders get passed 0, planes get passed 1, lines get 2, and points get 3 + -> ListLength -- ^ Previous shape of massinput -> (Text -> Text) -- ^ Nudge deterministic field ids so they're unique -> FieldView UniWorX -- ^ Submit-Button for this add-widget -> Maybe (Form (Map ListPosition Int -> FormResult (Map ListPosition Int))) -- ^ Nothing iff adding further cells in this position/dimension makes no sense; returns callback to determine index of new cells and data needed to initialize cells - mkAddForm 0 0 nudge submitBtn = Just $ \csrf -> do + mkAddForm 0 0 liveliness nudge submitBtn = guardOn (allowAdd 0 0 liveliness) $ \csrf -> do (addRes, addView) <- mpreq textField ("" & addName (nudge "text")) Nothing -- Any old field; for demonstration let addRes' = fromMaybe 0 . readMay . Text.filter isDigit <$> addRes -- Do something semi-interesting on the result of the @textField@ to demonstrate that further processing can be done addRes'' = addRes' <&> \dat prev -> FormSuccess (Map.singleton (maybe 0 (succ . fst) $ Map.lookupMax prev) dat) -- Construct the callback to determine new cell positions and data within @FormResult@ as required, nested @FormResult@ allows aborting the add depending on previous data return (addRes'', toWidget csrf >> fvWidget addView >> fvWidget submitBtn) - mkAddForm _pos _dim _ _ = error "Dimension and Position is always 0 for our 1-dimensional form" + mkAddForm _pos _dim _ _ _ = error "Dimension and Position is always 0 for our 1-dimensional form" -- | Make a single massInput-Cell -- @@ -184,8 +185,9 @@ postAdminTestR = do -- The actual call to @massInput@ is comparatively simple: - ((miResult, fvWidget -> miForm), miEnc) <- runFormPost . identifyForm ("massinput" :: Text) $ massInput (MassInput mkAddForm mkCellForm deleteCell allowAdd (\_ _ _ -> Set.empty) buttonAction defaultMiLayout ("massinput" :: Text)) "" True Nothing + ((miResult, fvWidget -> miForm), miEnc) <- runFormPost . identifyForm ("massinput" :: Text) $ massInput (MassInput mkAddForm mkCellForm deleteCell (\_ _ _ -> Set.empty) buttonAction defaultMiLayout ("massinput" :: Text)) "" True Nothing + ((i18nResult, fvWidget -> i18nWidget), i18nEnc) <- runFormPost . identifyForm ("i18n-stored-markup" :: Text) $ i18nField htmlField True (\_ -> Nothing) ("i18n-stored-markup" :: Text) "" True Nothing testDownloadWidget <- testDownload @@ -228,6 +230,29 @@ postAdminTestR = do

#{tshow res} |] + + i18nIdent <- newIdent + let i18nForm' = wrapForm i18nWidget FormSettings + { formMethod = POST + , formAction = Just . SomeRoute $ AdminTestR :#: i18nIdent + , formEncoding = i18nEnc + , formAttrs = [] + , formSubmit = FormSubmit + , formAnchor = Just i18nIdent + } + [whamlet| +

I18n-Form + ^{i18nForm'} + $case i18nResult + $of FormMissing + $of FormFailure errs +