Skip to main content

Learning - Releases

← App details

Nextcloud 35

Learning 5.4.0
Release Details
UpdatedAug. 24, 2026, 4:05 a.m.
Changelog

Prompted by a report from a system administrator piloting Learning at a manufacturing company in Vinnytsia, Ukraine (#3), who raised two problems: parts of the interface appear only in English or German, and there is no manual. Both turned out to be real defects, and the first one was considerably larger than the report suggested.

Added

  • Ukrainian interface language (uk) — the sixth language, complete at 2,940 keys in the main catalogue plus 252 in the VirtuProf runtime catalogue. Plural form is the three-form Slavic rule, not the two-form default.
  • This is a first pass and has not been reviewed by a native speaker. It is labelled as such in the manual, with instructions for submitting corrections without touching code.
  • docs/MANUAL.md — a user and administrator manual covering the chain the reporter asked for: course → learning materials → pool → questions → students → test attempt → results → completion, plus admin setup, the occ commands, mandatory training and compliance, the AI consent model, and how to add or fix a translation.
  • Linked from README.md and from a new <documentation> element in info.xml. That element was absent, so the App Store listing offered no documentation link at all — the direct reason the reporter could not find any.
  • Two new i18n gates, both wired into check-i18n-parity.sh:
  • scripts/check-i18n-coverage.py compares the source against the catalogue. Gates 1–3 all compare the language files against each other, so a string missing from all of them at once passes every one of them. Verified by reconstructing the defect: removing a key from all six files leaves gates 1–3 green and turns this one red. It covers JS/Vue t()/n(), PHP $l->t() and the indirect toolCatalog.js keys.
  • scripts/check-i18n-placeholders.py asserts that every translated value carries the same placeholders ({n}, %1$s, …) as its German source, and that no value is written in the wrong script. With six languages and machine-assisted translation, a dropped placeholder became the likeliest remaining defect.

Fixed

  • 635 interface strings were absent from the translation catalogue entirely. They were passed through t() but had no entry in de.json, so Nextcloud's l10n fell back to the source string and rendered them in German in every language, including English.
  • 618 came from JS/Vue, 10 from PHP $l->t() calls (notification subjects, the legal notice and privacy links), and 7 from toolCatalog.js, which reaches t() through a variable and is invisible to any literal scan.
  • The most-used casualties: the course materials tab almost in full, parts of course administration, and the answer-type selector in the question form — Einzelauswahl / Mehrfachauswahl / Freitext, the three options every author picks between when writing a question.
  • All 635 are now in the catalogue and translated into all six languages.
  • VirtuProf keeps its own 252-key runtime catalogue (src/l10n/virtuprof-strings.js) which is separate from l10n/*.json and was not covered by any gate. A language added to the main catalogue alone would leave the assistant speaking German or English. It now carries a uk dictionary, and virtuprof-i18n.js lists it.
  • VirtuProf's backend language allow-list silently discarded valid choices. VirtuProfController::ALLOWED_INTERFACE_LANGUAGES did not contain fr, although the frontend has offered French all along; normalizeInterfaceLanguage() coerces anything unlisted to '' and returns no error, so a French user's choice was dropped without a trace. Both fr and uk are now listed, with a comment naming the three places that must stay in sync.
  • scripts/deploy-prod.sh synced a hard-coded five-language list. A new language would have been generated, guarded and committed — and then never copied to the server. Now six, alongside l10n_js_sync.py and check-i18n-parity.sh.

Known limits

  • Some strings still reach t() through variables that the coverage gate cannot enumerate (onboarding slide definitions, dashboard tiles, a few config objects). Run python3 scripts/check-i18n-coverage.py --list-indirect to see the remaining call sites. Those were not audited in this pass.
  • The content language layer — optional parallel translations of question text, held in learning_q_translations / learning_a_translations — still accepts only de, en, ru, ar. Adding uk means a DB migration against a CHECK constraint and a re-test on both PostgreSQL and MariaDB, so it is deliberately out of scope here. It is not a blocker for Ukrainian use: questions authored directly in Ukrainian are stored and displayed unchanged, because the base question row has no language constraint.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturefOjauQfAoTxzZhK43pVS3uOvGA7B9xGJSgoX542rNXSSR5ofRPlwyghfUAWDO204V4VIxT68tuEawloJMFAYYs3FnBjPkCG4oFn/yLxRRrnsKasZD+igQe3RyfsLFOR0xR0MmUlvBYVeQxmSOZY+KDiHS7ED9FDgPMuHeOC5thDa6+dKZDzkBlNKmcLlIHk/Fop73pfVV8XDCS2VF/mwwt2/DxSc715n+KGoUQJ6InShXf9AYlB2ZralLwTPEBHocZvg910Xzt7Co34DkQT5dQmnzHj3uS8kKp+Jxou6U+lCnttxLTovxsmZnnmhF5kaHyonmTYa4QxzuXmeqTDRCHkd/Row7bnTy2MsAEqmS0M/KF/pAW6m++1mABO9Axe318aewrbPfZp3kei9FHohI+FAZMt0DQ3e5Bmp+RChArWS8ndS6aPrrFjA87zym4FvxXRbf8hfyYVvihBWHmefgEwXcbJZwwe6cOBjiQloin97i7AhuPh1YdscWSrEjZbkdZnjV0Vvl6BhKV2X3oAQIf6yySfKe60qw1F/gqz6fB4NG5ngDosDBjVcgT363g6rNng8brp89/g6gk7rN8+PyLttCyTxnzWcGVXdyujtFjS2frX47blARJOXhwD/PpmhSHJRsxE6e7vIk+HrgHqvkaDGuy/15WnKYu64AfeFauo=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.3.1
Release Details
UpdatedAug. 22, 2026, 9:29 a.m.
Changelog

Fixed

  • Every AI endpoint returned HTTP 500 instead of working (#3). Nextcloud derives the controller class from the route name, so 'name' => 'ai#available' resolves to OCA\Learning\Controller\AiController. The file was named AIController.php and declared class AIController; because PSR-4 autoloading is case-sensitive, the class was never found and the App Framework threw QueryNotFoundException before the request reached any of our code. All five ai#* routes — available, generate, status, import and explain — have been broken this way since they were introduced in v2.7.0.
  • Visible effect was mostly log noise: the callers in QuestionList.vue, LeitnerMode.vue and TrainingMode.vue swallow the failure and fall back to aiAvailable = false, so the AI buttons simply never appeared. Nothing was lost, but nothing worked either, and every page that asked wrote a stack trace to nextcloud.log.
  • The class-resolution failure happens before the authentication middleware, which is why the endpoint answered 500 to anonymous callers where every healthy route answers 401. That difference is what pinned the diagnosis.

  • Four SQL queries referenced columns that do not exist, each failing with SQLSTATE 42703 every time it ran. All four were invisible to PHPStan — the defect lives in a string — and each sat behind a catch (\Throwable) that turned the error into a 500 or an empty result.

  • LernprofilService: c.name on learning_courses, which has title. In enrichPoolsWithCourseData this broke /api/profile/skill-map outright (since 2026-04-09); the identical mistake in the cheat-sheet query hit only users who actually had wrong answers to report, so it stayed hidden behind an empty-result early return on fresh accounts.
  • RagContextService and CourseSummaryService: ua.user_id on learning_user_answers, which has no user column at all — the user hangs off the session. Both now join learning_sessions the way NoteGeneratorService already did. User weaknesses and trouble spots never loaded; they threw.
  • CourseSummaryService additionally compared the boolean is_correct against the integer 1, a type error on PostgreSQL. It now tests the column directly, which is portable across PostgreSQL and MariaDB.
  • NoteGeneratorService: chapter_ref on learning_pools, a column that never existed (the chapter columns are chapter_key / chapter_title / chapter_order). Every authorised POST /api/notes/generate died in this query, before reaching the model at all.
  • RagContextService: name on learning_courses. This is the first query buildContext() runs once a course is set, and a shared catch aborted the whole build after it — so VirtuProf answered without any course context (no chunks, pool questions, Leitner stats or weaknesses) and without reporting an error.
  • CourseSummaryService: winner_uid on learning_duel_sessions. That column only exists on the league tables; a duel's winner follows from the scores. This query is on the ordinary course-summary path, so it took summaries, snapshots, narratives and CSV exports down with it. Wins are now counted from the scores of finished duels — and so is the total, because a duel that is still waiting for an opponent or expired unplayed has no winner and would otherwise sit in the denominator of win_rate.
  • FeedController reported no course names. It called getName() on the course entity, which only has getTitle(); the surrounding catch turned that into course_name: null for every feed item rather than an error.
  • Skill map and cheat sheet exposed courses the user is not in. enrichPoolsWithCourseData accepted a user id and then ignored it, so it returned every course any accessible pool had been added to. An instructor can attach a pool that was merely edit-shared with them, which handed the pool's owner the id and title of a course they are not a member of. Both queries now apply the same membership test the rest of the service already used, and both order their results — a pool can belong to several courses and the mapping keeps the first row, which without an explicit order was whatever the database happened to return first.
  • LernprofilController logged nothing when it failed. All five catch (\Throwable) blocks returned a 500 without touching the logger, which is why nextcloud.log held no trace of an endpoint that had been broken since April 2026. They now log the exception before returning.

Security

  • Duels and gameshows could be started on any pool. DuelService::createDuel and GameshowService::createSession only checked pool access when a courseId was supplied, and both endpoints accept none — so two cooperating users could start a session on an arbitrary pool id and read its questions, its answer options, and after answering the correct answer id. TrainingService has always gated its equivalent path. Both now call the same check, which is made public on QuestionService rather than copied a sixth time. Verified live in both directions: a pool the caller cannot reach is refused, one they can reach still works.
  • The Wissensturm category endpoint had the same hole from a different angle: it took any pool id as the chosen category and read a question from it. It is now authorised against the session — the pool the session was created with, or any pool of its course — rather than against the caller's own pool access, because players who join a session legitimately are not required to have access to its pool.
  • POST /api/courses/{id}/summary/snapshot did not check course access. Every other endpoint in SummaryController passes through CourseService::findById() first; this one called the service directly, so any logged-in user could snapshot an arbitrary course id and read the result back through their own snapshot. It now applies the same gate and answers 403.

Added

  • scripts/check-sql-columns.py — resolves every alias-qualified column reference in app/lib against the live database schema. It found all four SQL defects above in one run.
  • ClassResolutionTest — walks both places where Nextcloud builds a class name from a string (appinfo/routes.php route names and the FQCNs in appinfo/info.xml) and asserts each one resolves to a file whose name matches exactly, plus that the file declares the class name the router will ask for. Neither PHPStan nor the controllers' own unit tests can catch this: those tests use the real class name, so they load and pass while HTTP is broken.

Changed

  • .githooks/pre-push now runs PHPUnit and the SQL column check. Neither the PHP test suite nor any schema check ran in a mandatory gate before, which is the reason both classes of defect survived for months. Note that the versioned hook was not the active one — core.hooksPath pointed at a stale copy in .git/hooks.
  • The API test for /api/profile/skill-map asserts 200 and a payload shape; it accepted 500 before, and so reported a hard SQL error as passing for four months.
  • The API test for /api/ai/available asserts 200 and a boolean payload. It previously accepted 200 or 500, which is what let a total outage ship through 23 tagged releases over five months — the endpoint always answers 200 with {available:bool}, including when no API key is configured, so a 500 was never an acceptable outcome.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYUSX7HQellMvxhF9ONH3kjJE27MJqyFeN7kD1SUBZ+IQWY1+4g0xdnfNr3eblyATtg5gdSTqi34Si6TuDz4lETH0/R3jDPG/UHGjtqHpclzdXhTd3diZh1BvpU+XQhHH86Q+vukW5NUOak+MzINOX3JMH7Q0hklzyvEu0K0DnTiPvnTnilMWGeJfIWbc/isBOb+P1GlHCiEzZY4/KvDN78aPNeNBva/j3aLX4V26OJeIeIRVx5PN22Cmi0k1HAH8D8oGGG8HoNYxDmEmUgoIgsBeqrU+Y0tLwtcmTo6K2bjepBRHMEngiUe7ytyKS/ty/in+BbVIapESWNlNplIi+JvxwI+pczWj5/rnowOSsBvl/vj27LxHZm/sEcdqjICp2jbuVTJA734MwuzlAbZG6+XbD5BR8iF6GqNxzfhHCtDQ6V7N4XGQ7yKaaYDAOVsFPwKTSXRaOq9nF+yPADZS/fBij3uNmHk8IAWQjUAO54DZup3KeMM9f2TVWP8la+aEEYoaxVKHUOWfjEqUQ1ptE9UDs5Z8otOLM8FR5WM5xnNQ+N6qSJi2UrK53OtqzSzh2VQ+xIrDDn6NdjeRqayGDwC18cOI5UWixKHoPoic0FYHuPJztK82zy+yFTLz7bfYNJBl73uNTDc46+GIbXfmLKTh7g4em0rncGu4aC1I/Pc=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.3.0
Release Details
UpdatedAug. 18, 2026, 3:30 p.m.
Changelog

Added

  • occ learning:uninstall — removes everything this app leaves in the database, so the app can be uninstalled cleanly (#1). Dry run by default; --execute is required to change anything.
  • Drops all learning_* tables, including the legacy names from both historical rename waves, and clears the rows left in migrations, appconfig, preferences, jobs, notifications, activity and activity_mq.
  • Deletes the metadata rows before dropping the tables. Leaving the oc_migrations rows behind makes a later reinstall skip every migration and boot broken, so if a run dies between the two phases, this order leaves the recoverable state.
  • Refuses to run when issuer/certificate data exists unless --keep-certificates or --drop-certificates is chosen: the Ed25519 issuer key is unrecoverable, and losing it makes every certificate ever issued permanently unverifiable.
  • Refuses to run when any table cannot be read. An unreadable table is never mistaken for an empty one — every guess here is destructive.
  • Reports rather than touches what it cannot safely change: the theming legal links (rewritten by Application::boot() on every request, so they can only be cleared after app:remove — the command prints the statement), the Dashboard layout row, and the files the app created under /Learning/ in users' homes.
  • Tables carrying the learning_ prefix that this version does not know about are reported, not dropped.
  • scripts/db-uninstall-probe.php — runs the real command against a throwaway MariaDB and PostgreSQL, covering platform behaviour no unit test or PostgreSQL-only dev instance can reach.

Fixed

  • occ learning:import-pool-json no longer drops valid open questions. The pre-check demanded at least two answers for every question, which is right for multiple choice but wrong for open (fill-in) questions — those carry exactly one model answer and were skipped silently. The check now mirrors what QuestionService::create already enforced.

Changed

  • The privacy and imprint pages name the operating company, with the registration details an imprint requires, instead of a private individual.

Notes

  • No repair step is used for uninstall. Nextcloud executes <repair-steps><uninstall> from AppManager::disableApp(), so it also fires when an admin merely disables the app — a drop there would let one mis-click destroy every course, pool and certificate. The reasoning, including why a marker-armed variant was rejected, is recorded in UninstallCommand's class docblock.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureBIvlmCYXMWVkuElwUaidAWzPW5t6ZTALeGz+aQJFxs45ncQXSDopk45g5fBrZVXjcjOs7KerH3YpBwu3kbOMMPQ2x79O92Mkqa3zWpW2mVUXIpUOpgVUaXsEJcDIoz3kTiyrD0ZOnvrYgpQYTPy1r35QhANfB89BwhUreOIoz2/L7EryXoAzAg3GCaHT3KRARBGGaCUBEQvRlVh9X1gGvQtcOwYcE7nO2JpdXjVRrE17foqNtVwgDRKD3+CJDWwuGDeEe3F8pIGLtaWCS6ZhzRNluj3kFwafqjL4h7wo/m7lCPeNvd/gwKWdQ/QV0ZPG2zdkQGgLwDXW4V3pypNOpXgvtVRA2JEFW3dvLbCKOWTn68ntmsfuy6+WaNyFOjwq60YE2h5yBaXCFe8cT0Cbjo1pOotrAM0LNDPMe0Jz8ImV5tDkgTnzXIaD/rQWeyLv5VOdYgT/EFya1RKpQd59QxF5i4aOHl6j+M2o+LKUAZV0fcmO2vlZ0v3W5+aB7K2+fQOi2nl0MQ9f8fo+AmAJcDSe0SrFbOy5u9TMbzOV+nKcJmyJLBsN7uRtFxhAA6Zm9dvcJL7iTlJjr1q/uPWHiv+nAPpD5JNezDcFQ4jqfERzhhZbvA7CdozszMTyrbpTWPJfNKqU9ekyWDIn/XpinHOH1IsBggg07OKAEvFxlFQ=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.2.1
Release Details
UpdatedJuly 4, 2026, 12:03 p.m.
Changelog

A whole-app security audit (8 review lanes) followed by an adversarial multi-model pre-live review. Every finding was verified against the actual code before fixing; fixes shipping to the public App Store were themselves re-reviewed. No functional/feature changes — hardening only.

Security — Access control (IDOR)

  • Course-scoped authorization on RAG import/list/delete/moderate endpoints (an instructor of course A could previously touch course B's shared RAG pool); course-membership gates on RAG contribution and on listing one's own contributions.
  • Answer-key exports (CSV/JSON) require export-level access (owner, edit-share, or the instructor of a course the pool is attached to) — not plain read access; enrolled students can no longer pull answer keys.
  • Course-access gates on document index/material-folder and course feed endpoints (previously cross-course content/PII could leak).

Security — Exam integrity (oracle prevention)

  • During an active exam, explanations, correct answers, PBQ solution config and translated explanations are withheld across all surfaces that emit question-derived data (training/exam payloads, translation endpoints, the spaced-repetition due-queue, and VirtuProf's RAG + client-supplied question context).
  • Starting an exam no longer auto-completes an existing exam by switching course context; correct PBQ answers now increment the exam score even when per-question feedback is suppressed.

Security — AI / GDPR

  • Per-user AI consent is now enforced on every user-triggered LLM path (VirtuProf chat/interview, course summary, AI question generation, note generation, AI explanations) — the admin ai_enabled toggle alone is no longer sufficient. The frontend catches the consent-required response and points the user to the in-app consent dialog.
  • Stored chat memory is NFKC-normalized and injection-filtered before re-entering the system prompt (a payload stripped on turn 1 could previously reappear on turn 2); the injection sanitizer now covers the same role-change / prompt-leak / jailbreak markers as the detector.
  • File-intent note generation and chat-memory compression now count against the shared AI rate limit.

Security — Data-layer robustness

  • Repair migration for course-feature table-name drift (curriculum scopes, question overrides, announcements) — cross-DB safe (PostgreSQL 16 / MariaDB / SQLite), identifier-quoted, fail-fast on platform-detection error, and it aborts rather than orphaning legacy data if a rename fails.
  • Prefix-agnostic insert ids (exam slots, announcements) instead of a hardcoded oc_ sequence name; entity model aligned with the shipped schema.

Database

  • Version009900: idempotent, data-preserving repair of legacy learning_curriculum_scopes / learning_q_overrides / learning_announcementslearning_course_*. Verified on PostgreSQL 16.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYfFmoFuitzdd9S0evt388jKj56ydtqz4AABUiKwRxBS8pLpwDBnc1PPdvxLLLB/81ngsy7JN/Sair3bFET8GvCrWppH/QOJZQzxqC8DMsQLET+eXEW11WQrh7U2ON29fd+ai9BPh0c66Mb2U0Kshh1VbQhqtTOJHkLx9eV1KoeaMHLdc4euDJvIG2XxSJOIh+uhNOdfkIORIXSHDHL/iuyKvMTdlsq36zQqz86QNVOUKv43V7uFbaGK0t58E7W3IKYP8dAByprTS0Uck8vHf08V0qwfChHxJgl404ag7BCnxntM3aKq33FijSae8ts2EaP79OusLN+aJNmUXLfzYCT/UaE4POgzmDCHf6VrKwIPVymQ7dhNaw2zUjaqM/DBlzKxc9KvQNvGU25QiyjgGmMxXp4TvNIxaR5rbw/Qo3n683dZ7CqQmaNEsTK9k90BxlPnw8A4tQA3L+yQbkrjzlaNRIkyfYDdLyJq1Fz3W8aWlolDSPs5J1ya7z3WMOLxvhcmyZRAWnsKaJhth1P47kriikHRIbk1wZslfyi6PrG0sgpLIa5eiS8gIlbpZplE3h6i8lMvFa2Bi2qlGwU4QtaUDKPn3uE4ZL++p17oa4R1DZwJ3nbjC6ojjZBPa7zzLHAR21obaJ3mynkvIMZPfjBs8F7ZOUgNaeMqrH5LHOlY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.0.0
Release Details
UpdatedJune 28, 2026, 9:57 p.m.
Changelog

Versions 4.4.8 and 4.4.9 were internal migration-vehicle bumps and were never released; this entry covers everything since 4.4.7.

Added

  • Signed completion certificates. A course can define pass criteria (score threshold, validity period). When a learner passes, the app mints a cryptographically signed certificate — a W3C Verifiable Credential (Ed25519 / VC-JWT) bound to the learner and the course.
  • did:web issuer identity. Each instance is its own credential issuer via a path-based did:web derived from the server URL; the public key is published at the app's did.json. Issuer keys are generated with occ learning:cert:init-issuer, stored encrypted at rest (ICrypto), and can be rotated without invalidating previously issued certificates.
  • Public verification portal. Anyone can verify a certificate by its ID / QR code on a public page. Signature, key status, claim binding, revocation and expiry are checked as an AND-chain; unknown IDs leak no personal data. Course owners can revoke certificates.
  • Compliance report. Per-course completion/compliance report for instructors and admins (who passed, when, certificate status).
  • Certificate view. A printable, A4-ready certificate with QR code, a prominent Certificate ID and a verification link.

Changed

  • Avatars overhauled. The default assistant Nova is now a full chibi character with emotion-aware expressions (neutral / happy / sad / surprised / sleep / celebrate) instead of an abstract shape. The three scholar skins (Theoretiker / Kosmologe / Astrophysik-Popularisierer) were polished — gradient shading, larger eyes, brighter signature accents.

Setup

  • Certificates need a one-time key. Run occ learning:cert:init-issuer once per server to generate the issuer signing key. Until it exists, the admin settings page shows a reminder and no certificate is minted when a learner passes. See README → Installation → Certificates setup.

Database

  • Version009100 creates learning_cert_keys and learning_certificates; Version009200 adds revoked_at. Verified on PostgreSQL 16 and MariaDB (the credential_json / secret_key_enc TEXT columns are intentionally unindexed to avoid the utf8mb4 key-length limit).
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureQPVnxiwJELOU6fw/Mnt31lPo9vwC3rVQXMtlqzYe6bNyTaq9hE6cKJGl61stgSGJaokwCCZOK3xG9aMhVng80khs2OADvguhtH0u+75IQmVpVQBZs9F4Ksh+qjwoI/m/j7o37tSsNi8MT5AoZMGl5QztB3RIR6Ir6WXARaJ9LDLIAv01Ufh1Sh3QubLeR/qnRmi7aiTdSixVy3ipiTPSP99IQ8whFyWml6E3E78Yzn1hdE269W013YjN38xnAZSP34LAU2I2NKCwne9d5v3fIhu2Ky0zlMlpNWY1MAhEBMUZcRYAcT3HdVmGq6xnW783+PDJO2CMAvsWFHmrGAW+3XkaduHK0jPmLfBw7WygDnFnxZTmOERiocYQ9WPmb5JAjFsZ/1+C5iGL6ZY9k4Bkr7oGCXYOCAqGYAYdBc7sY/s8QRa20+r5GO3CQKOOKEgIEFfF0ydDKOIWz3wQwEf9iL/Mz5RMgbzXgo797W30G1nViHoIgoPVnAipitPJxgvms20mhtB/V6HbqSGpGc9P2uIOoxLXQk7tZ8W0vhK+DG486S4Pmmk2Vw9fpZ5Wm7kAoNj1JYJtfO1OZ8mcpxX7hSPLZ7ChtpARjEJzCuB2pv5kYiYOiVHXvnZ5ZGPkT47A6TzVUm5Mc0Lk+rUadRvJiRT/4P2h7TvMSh+ofOTkBkk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 4.4.7
Release Details
UpdatedJune 24, 2026, 8:26 a.m.
Changelog

Fixed

  • learning_user_stats had no primary key (#24). Reported by @haecks on a fresh NC 33.0.5 install: Nextcloud logged "Table 'oc_learning_user_stats' has no primary key and therefore will not behave sane in clustered setups. This will throw an exception and not be installable in a future version of Nextcloud." The table was created back in Version001200 with only a unique index (learn_ustats_user_uniq) on user_id, never a primary key. Since there is exactly one stats row per user and every access path addresses the table by user_id, user_id is the natural primary key. A new migration (Version008000) drops the now-redundant unique index and promotes user_id to the primary key. The migration is idempotent (guards for missing table/column and an already-present primary key) so it is safe on existing installs and re-runs. Verified live on PostgreSQL 16 and independently on MariaDB 11.4 with utf8mb4 (the varchar(64) key stays well under InnoDB's index-length limit).

Fixed

  • v4.4.5 shipped without the data/ directory (starter pools, campaigns, simulator scenarios, privacy-info.json, PWA guide, course materials — 77 files). The release build followed a cp file-list that omitted data/, so on a clean v4.4.5 install the "Add starter pool" feature, campaigns, the practicum/simulator scenarios and the privacy page had no backing data. v4.4.6 restores data/ (verified: tarball file-set now matches the last-known-good v4.4.4 manifest). The i18n content of v4.4.5 (#21 + #19) is unchanged and included.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureftN/YerXrcUvkvumEZrPZISqpuiPEQ8z9LhPDuW05huisHlIYypdnqnbJbDtVB0cRS/4wHWWZqLS66JZbEYnaiyq5FzvS27ama++aIoPDUjCnbO1kT0dZKegpMuJbe4/EZIhMoRgfFGDd7j8Tz21tNa2oAA5ifka1JMgdZ3zyuGkNE4N3PHwszsZY0PYM3Vk/KOX/NKZJDQQY+B1xURqqULtDn+X5vO9uumUU4gyZTWY9x22Sf2rRg4Tt9MdKIkKo9AdCT18OPc0ymsQQU0Ijp01cw7M2hopuGvcnBUjYJV0ALDw1ChMJjXEjxMM8GK1bST+Ge9Mwc31vKIhb3NC54p58m3DmFdkL/+pSXSWCGWdIJ/hOBlrVDVssSVXpqaFAjL0k0owI6O+ZHD2cnIKX0Nc2tUblKgMZmEtX75TLeZ8wENMYTcOVe56+9jCYarHKVEsL1Nx0i4XbqoN/6OTXSyrVbxG3barZL58DCO6Nla+kUFjILEsbV88eIhl+2EYuidefDeIbr4SMNYU+JabbgFj9/4QO8lYuoah7r4ox/RHvKhM8koNabXuPngLv3XbQY4rb7qJqfrkbSzFJuHCGPUlgZLWfGIfEf+euCrkwWlQdjFjQhFh73gHveHttfc6ZpOUttCgza6qSZ7GliplIxEuYGjC6BxEKEB/5mka84g=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 34

Learning 5.4.0
Release Details
UpdatedAug. 24, 2026, 4:05 a.m.
Changelog

Prompted by a report from a system administrator piloting Learning at a manufacturing company in Vinnytsia, Ukraine (#3), who raised two problems: parts of the interface appear only in English or German, and there is no manual. Both turned out to be real defects, and the first one was considerably larger than the report suggested.

Added

  • Ukrainian interface language (uk) — the sixth language, complete at 2,940 keys in the main catalogue plus 252 in the VirtuProf runtime catalogue. Plural form is the three-form Slavic rule, not the two-form default.
  • This is a first pass and has not been reviewed by a native speaker. It is labelled as such in the manual, with instructions for submitting corrections without touching code.
  • docs/MANUAL.md — a user and administrator manual covering the chain the reporter asked for: course → learning materials → pool → questions → students → test attempt → results → completion, plus admin setup, the occ commands, mandatory training and compliance, the AI consent model, and how to add or fix a translation.
  • Linked from README.md and from a new <documentation> element in info.xml. That element was absent, so the App Store listing offered no documentation link at all — the direct reason the reporter could not find any.
  • Two new i18n gates, both wired into check-i18n-parity.sh:
  • scripts/check-i18n-coverage.py compares the source against the catalogue. Gates 1–3 all compare the language files against each other, so a string missing from all of them at once passes every one of them. Verified by reconstructing the defect: removing a key from all six files leaves gates 1–3 green and turns this one red. It covers JS/Vue t()/n(), PHP $l->t() and the indirect toolCatalog.js keys.
  • scripts/check-i18n-placeholders.py asserts that every translated value carries the same placeholders ({n}, %1$s, …) as its German source, and that no value is written in the wrong script. With six languages and machine-assisted translation, a dropped placeholder became the likeliest remaining defect.

Fixed

  • 635 interface strings were absent from the translation catalogue entirely. They were passed through t() but had no entry in de.json, so Nextcloud's l10n fell back to the source string and rendered them in German in every language, including English.
  • 618 came from JS/Vue, 10 from PHP $l->t() calls (notification subjects, the legal notice and privacy links), and 7 from toolCatalog.js, which reaches t() through a variable and is invisible to any literal scan.
  • The most-used casualties: the course materials tab almost in full, parts of course administration, and the answer-type selector in the question form — Einzelauswahl / Mehrfachauswahl / Freitext, the three options every author picks between when writing a question.
  • All 635 are now in the catalogue and translated into all six languages.
  • VirtuProf keeps its own 252-key runtime catalogue (src/l10n/virtuprof-strings.js) which is separate from l10n/*.json and was not covered by any gate. A language added to the main catalogue alone would leave the assistant speaking German or English. It now carries a uk dictionary, and virtuprof-i18n.js lists it.
  • VirtuProf's backend language allow-list silently discarded valid choices. VirtuProfController::ALLOWED_INTERFACE_LANGUAGES did not contain fr, although the frontend has offered French all along; normalizeInterfaceLanguage() coerces anything unlisted to '' and returns no error, so a French user's choice was dropped without a trace. Both fr and uk are now listed, with a comment naming the three places that must stay in sync.
  • scripts/deploy-prod.sh synced a hard-coded five-language list. A new language would have been generated, guarded and committed — and then never copied to the server. Now six, alongside l10n_js_sync.py and check-i18n-parity.sh.

Known limits

  • Some strings still reach t() through variables that the coverage gate cannot enumerate (onboarding slide definitions, dashboard tiles, a few config objects). Run python3 scripts/check-i18n-coverage.py --list-indirect to see the remaining call sites. Those were not audited in this pass.
  • The content language layer — optional parallel translations of question text, held in learning_q_translations / learning_a_translations — still accepts only de, en, ru, ar. Adding uk means a DB migration against a CHECK constraint and a re-test on both PostgreSQL and MariaDB, so it is deliberately out of scope here. It is not a blocker for Ukrainian use: questions authored directly in Ukrainian are stored and displayed unchanged, because the base question row has no language constraint.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturefOjauQfAoTxzZhK43pVS3uOvGA7B9xGJSgoX542rNXSSR5ofRPlwyghfUAWDO204V4VIxT68tuEawloJMFAYYs3FnBjPkCG4oFn/yLxRRrnsKasZD+igQe3RyfsLFOR0xR0MmUlvBYVeQxmSOZY+KDiHS7ED9FDgPMuHeOC5thDa6+dKZDzkBlNKmcLlIHk/Fop73pfVV8XDCS2VF/mwwt2/DxSc715n+KGoUQJ6InShXf9AYlB2ZralLwTPEBHocZvg910Xzt7Co34DkQT5dQmnzHj3uS8kKp+Jxou6U+lCnttxLTovxsmZnnmhF5kaHyonmTYa4QxzuXmeqTDRCHkd/Row7bnTy2MsAEqmS0M/KF/pAW6m++1mABO9Axe318aewrbPfZp3kei9FHohI+FAZMt0DQ3e5Bmp+RChArWS8ndS6aPrrFjA87zym4FvxXRbf8hfyYVvihBWHmefgEwXcbJZwwe6cOBjiQloin97i7AhuPh1YdscWSrEjZbkdZnjV0Vvl6BhKV2X3oAQIf6yySfKe60qw1F/gqz6fB4NG5ngDosDBjVcgT363g6rNng8brp89/g6gk7rN8+PyLttCyTxnzWcGVXdyujtFjS2frX47blARJOXhwD/PpmhSHJRsxE6e7vIk+HrgHqvkaDGuy/15WnKYu64AfeFauo=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.3.1
Release Details
UpdatedAug. 22, 2026, 9:29 a.m.
Changelog

Fixed

  • Every AI endpoint returned HTTP 500 instead of working (#3). Nextcloud derives the controller class from the route name, so 'name' => 'ai#available' resolves to OCA\Learning\Controller\AiController. The file was named AIController.php and declared class AIController; because PSR-4 autoloading is case-sensitive, the class was never found and the App Framework threw QueryNotFoundException before the request reached any of our code. All five ai#* routes — available, generate, status, import and explain — have been broken this way since they were introduced in v2.7.0.
  • Visible effect was mostly log noise: the callers in QuestionList.vue, LeitnerMode.vue and TrainingMode.vue swallow the failure and fall back to aiAvailable = false, so the AI buttons simply never appeared. Nothing was lost, but nothing worked either, and every page that asked wrote a stack trace to nextcloud.log.
  • The class-resolution failure happens before the authentication middleware, which is why the endpoint answered 500 to anonymous callers where every healthy route answers 401. That difference is what pinned the diagnosis.

  • Four SQL queries referenced columns that do not exist, each failing with SQLSTATE 42703 every time it ran. All four were invisible to PHPStan — the defect lives in a string — and each sat behind a catch (\Throwable) that turned the error into a 500 or an empty result.

  • LernprofilService: c.name on learning_courses, which has title. In enrichPoolsWithCourseData this broke /api/profile/skill-map outright (since 2026-04-09); the identical mistake in the cheat-sheet query hit only users who actually had wrong answers to report, so it stayed hidden behind an empty-result early return on fresh accounts.
  • RagContextService and CourseSummaryService: ua.user_id on learning_user_answers, which has no user column at all — the user hangs off the session. Both now join learning_sessions the way NoteGeneratorService already did. User weaknesses and trouble spots never loaded; they threw.
  • CourseSummaryService additionally compared the boolean is_correct against the integer 1, a type error on PostgreSQL. It now tests the column directly, which is portable across PostgreSQL and MariaDB.
  • NoteGeneratorService: chapter_ref on learning_pools, a column that never existed (the chapter columns are chapter_key / chapter_title / chapter_order). Every authorised POST /api/notes/generate died in this query, before reaching the model at all.
  • RagContextService: name on learning_courses. This is the first query buildContext() runs once a course is set, and a shared catch aborted the whole build after it — so VirtuProf answered without any course context (no chunks, pool questions, Leitner stats or weaknesses) and without reporting an error.
  • CourseSummaryService: winner_uid on learning_duel_sessions. That column only exists on the league tables; a duel's winner follows from the scores. This query is on the ordinary course-summary path, so it took summaries, snapshots, narratives and CSV exports down with it. Wins are now counted from the scores of finished duels — and so is the total, because a duel that is still waiting for an opponent or expired unplayed has no winner and would otherwise sit in the denominator of win_rate.
  • FeedController reported no course names. It called getName() on the course entity, which only has getTitle(); the surrounding catch turned that into course_name: null for every feed item rather than an error.
  • Skill map and cheat sheet exposed courses the user is not in. enrichPoolsWithCourseData accepted a user id and then ignored it, so it returned every course any accessible pool had been added to. An instructor can attach a pool that was merely edit-shared with them, which handed the pool's owner the id and title of a course they are not a member of. Both queries now apply the same membership test the rest of the service already used, and both order their results — a pool can belong to several courses and the mapping keeps the first row, which without an explicit order was whatever the database happened to return first.
  • LernprofilController logged nothing when it failed. All five catch (\Throwable) blocks returned a 500 without touching the logger, which is why nextcloud.log held no trace of an endpoint that had been broken since April 2026. They now log the exception before returning.

Security

  • Duels and gameshows could be started on any pool. DuelService::createDuel and GameshowService::createSession only checked pool access when a courseId was supplied, and both endpoints accept none — so two cooperating users could start a session on an arbitrary pool id and read its questions, its answer options, and after answering the correct answer id. TrainingService has always gated its equivalent path. Both now call the same check, which is made public on QuestionService rather than copied a sixth time. Verified live in both directions: a pool the caller cannot reach is refused, one they can reach still works.
  • The Wissensturm category endpoint had the same hole from a different angle: it took any pool id as the chosen category and read a question from it. It is now authorised against the session — the pool the session was created with, or any pool of its course — rather than against the caller's own pool access, because players who join a session legitimately are not required to have access to its pool.
  • POST /api/courses/{id}/summary/snapshot did not check course access. Every other endpoint in SummaryController passes through CourseService::findById() first; this one called the service directly, so any logged-in user could snapshot an arbitrary course id and read the result back through their own snapshot. It now applies the same gate and answers 403.

Added

  • scripts/check-sql-columns.py — resolves every alias-qualified column reference in app/lib against the live database schema. It found all four SQL defects above in one run.
  • ClassResolutionTest — walks both places where Nextcloud builds a class name from a string (appinfo/routes.php route names and the FQCNs in appinfo/info.xml) and asserts each one resolves to a file whose name matches exactly, plus that the file declares the class name the router will ask for. Neither PHPStan nor the controllers' own unit tests can catch this: those tests use the real class name, so they load and pass while HTTP is broken.

Changed

  • .githooks/pre-push now runs PHPUnit and the SQL column check. Neither the PHP test suite nor any schema check ran in a mandatory gate before, which is the reason both classes of defect survived for months. Note that the versioned hook was not the active one — core.hooksPath pointed at a stale copy in .git/hooks.
  • The API test for /api/profile/skill-map asserts 200 and a payload shape; it accepted 500 before, and so reported a hard SQL error as passing for four months.
  • The API test for /api/ai/available asserts 200 and a boolean payload. It previously accepted 200 or 500, which is what let a total outage ship through 23 tagged releases over five months — the endpoint always answers 200 with {available:bool}, including when no API key is configured, so a 500 was never an acceptable outcome.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYUSX7HQellMvxhF9ONH3kjJE27MJqyFeN7kD1SUBZ+IQWY1+4g0xdnfNr3eblyATtg5gdSTqi34Si6TuDz4lETH0/R3jDPG/UHGjtqHpclzdXhTd3diZh1BvpU+XQhHH86Q+vukW5NUOak+MzINOX3JMH7Q0hklzyvEu0K0DnTiPvnTnilMWGeJfIWbc/isBOb+P1GlHCiEzZY4/KvDN78aPNeNBva/j3aLX4V26OJeIeIRVx5PN22Cmi0k1HAH8D8oGGG8HoNYxDmEmUgoIgsBeqrU+Y0tLwtcmTo6K2bjepBRHMEngiUe7ytyKS/ty/in+BbVIapESWNlNplIi+JvxwI+pczWj5/rnowOSsBvl/vj27LxHZm/sEcdqjICp2jbuVTJA734MwuzlAbZG6+XbD5BR8iF6GqNxzfhHCtDQ6V7N4XGQ7yKaaYDAOVsFPwKTSXRaOq9nF+yPADZS/fBij3uNmHk8IAWQjUAO54DZup3KeMM9f2TVWP8la+aEEYoaxVKHUOWfjEqUQ1ptE9UDs5Z8otOLM8FR5WM5xnNQ+N6qSJi2UrK53OtqzSzh2VQ+xIrDDn6NdjeRqayGDwC18cOI5UWixKHoPoic0FYHuPJztK82zy+yFTLz7bfYNJBl73uNTDc46+GIbXfmLKTh7g4em0rncGu4aC1I/Pc=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.3.0
Release Details
UpdatedAug. 18, 2026, 3:30 p.m.
Changelog

Added

  • occ learning:uninstall — removes everything this app leaves in the database, so the app can be uninstalled cleanly (#1). Dry run by default; --execute is required to change anything.
  • Drops all learning_* tables, including the legacy names from both historical rename waves, and clears the rows left in migrations, appconfig, preferences, jobs, notifications, activity and activity_mq.
  • Deletes the metadata rows before dropping the tables. Leaving the oc_migrations rows behind makes a later reinstall skip every migration and boot broken, so if a run dies between the two phases, this order leaves the recoverable state.
  • Refuses to run when issuer/certificate data exists unless --keep-certificates or --drop-certificates is chosen: the Ed25519 issuer key is unrecoverable, and losing it makes every certificate ever issued permanently unverifiable.
  • Refuses to run when any table cannot be read. An unreadable table is never mistaken for an empty one — every guess here is destructive.
  • Reports rather than touches what it cannot safely change: the theming legal links (rewritten by Application::boot() on every request, so they can only be cleared after app:remove — the command prints the statement), the Dashboard layout row, and the files the app created under /Learning/ in users' homes.
  • Tables carrying the learning_ prefix that this version does not know about are reported, not dropped.
  • scripts/db-uninstall-probe.php — runs the real command against a throwaway MariaDB and PostgreSQL, covering platform behaviour no unit test or PostgreSQL-only dev instance can reach.

Fixed

  • occ learning:import-pool-json no longer drops valid open questions. The pre-check demanded at least two answers for every question, which is right for multiple choice but wrong for open (fill-in) questions — those carry exactly one model answer and were skipped silently. The check now mirrors what QuestionService::create already enforced.

Changed

  • The privacy and imprint pages name the operating company, with the registration details an imprint requires, instead of a private individual.

Notes

  • No repair step is used for uninstall. Nextcloud executes <repair-steps><uninstall> from AppManager::disableApp(), so it also fires when an admin merely disables the app — a drop there would let one mis-click destroy every course, pool and certificate. The reasoning, including why a marker-armed variant was rejected, is recorded in UninstallCommand's class docblock.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureBIvlmCYXMWVkuElwUaidAWzPW5t6ZTALeGz+aQJFxs45ncQXSDopk45g5fBrZVXjcjOs7KerH3YpBwu3kbOMMPQ2x79O92Mkqa3zWpW2mVUXIpUOpgVUaXsEJcDIoz3kTiyrD0ZOnvrYgpQYTPy1r35QhANfB89BwhUreOIoz2/L7EryXoAzAg3GCaHT3KRARBGGaCUBEQvRlVh9X1gGvQtcOwYcE7nO2JpdXjVRrE17foqNtVwgDRKD3+CJDWwuGDeEe3F8pIGLtaWCS6ZhzRNluj3kFwafqjL4h7wo/m7lCPeNvd/gwKWdQ/QV0ZPG2zdkQGgLwDXW4V3pypNOpXgvtVRA2JEFW3dvLbCKOWTn68ntmsfuy6+WaNyFOjwq60YE2h5yBaXCFe8cT0Cbjo1pOotrAM0LNDPMe0Jz8ImV5tDkgTnzXIaD/rQWeyLv5VOdYgT/EFya1RKpQd59QxF5i4aOHl6j+M2o+LKUAZV0fcmO2vlZ0v3W5+aB7K2+fQOi2nl0MQ9f8fo+AmAJcDSe0SrFbOy5u9TMbzOV+nKcJmyJLBsN7uRtFxhAA6Zm9dvcJL7iTlJjr1q/uPWHiv+nAPpD5JNezDcFQ4jqfERzhhZbvA7CdozszMTyrbpTWPJfNKqU9ekyWDIn/XpinHOH1IsBggg07OKAEvFxlFQ=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.2.1
Release Details
UpdatedJuly 4, 2026, 12:03 p.m.
Changelog

A whole-app security audit (8 review lanes) followed by an adversarial multi-model pre-live review. Every finding was verified against the actual code before fixing; fixes shipping to the public App Store were themselves re-reviewed. No functional/feature changes — hardening only.

Security — Access control (IDOR)

  • Course-scoped authorization on RAG import/list/delete/moderate endpoints (an instructor of course A could previously touch course B's shared RAG pool); course-membership gates on RAG contribution and on listing one's own contributions.
  • Answer-key exports (CSV/JSON) require export-level access (owner, edit-share, or the instructor of a course the pool is attached to) — not plain read access; enrolled students can no longer pull answer keys.
  • Course-access gates on document index/material-folder and course feed endpoints (previously cross-course content/PII could leak).

Security — Exam integrity (oracle prevention)

  • During an active exam, explanations, correct answers, PBQ solution config and translated explanations are withheld across all surfaces that emit question-derived data (training/exam payloads, translation endpoints, the spaced-repetition due-queue, and VirtuProf's RAG + client-supplied question context).
  • Starting an exam no longer auto-completes an existing exam by switching course context; correct PBQ answers now increment the exam score even when per-question feedback is suppressed.

Security — AI / GDPR

  • Per-user AI consent is now enforced on every user-triggered LLM path (VirtuProf chat/interview, course summary, AI question generation, note generation, AI explanations) — the admin ai_enabled toggle alone is no longer sufficient. The frontend catches the consent-required response and points the user to the in-app consent dialog.
  • Stored chat memory is NFKC-normalized and injection-filtered before re-entering the system prompt (a payload stripped on turn 1 could previously reappear on turn 2); the injection sanitizer now covers the same role-change / prompt-leak / jailbreak markers as the detector.
  • File-intent note generation and chat-memory compression now count against the shared AI rate limit.

Security — Data-layer robustness

  • Repair migration for course-feature table-name drift (curriculum scopes, question overrides, announcements) — cross-DB safe (PostgreSQL 16 / MariaDB / SQLite), identifier-quoted, fail-fast on platform-detection error, and it aborts rather than orphaning legacy data if a rename fails.
  • Prefix-agnostic insert ids (exam slots, announcements) instead of a hardcoded oc_ sequence name; entity model aligned with the shipped schema.

Database

  • Version009900: idempotent, data-preserving repair of legacy learning_curriculum_scopes / learning_q_overrides / learning_announcementslearning_course_*. Verified on PostgreSQL 16.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYfFmoFuitzdd9S0evt388jKj56ydtqz4AABUiKwRxBS8pLpwDBnc1PPdvxLLLB/81ngsy7JN/Sair3bFET8GvCrWppH/QOJZQzxqC8DMsQLET+eXEW11WQrh7U2ON29fd+ai9BPh0c66Mb2U0Kshh1VbQhqtTOJHkLx9eV1KoeaMHLdc4euDJvIG2XxSJOIh+uhNOdfkIORIXSHDHL/iuyKvMTdlsq36zQqz86QNVOUKv43V7uFbaGK0t58E7W3IKYP8dAByprTS0Uck8vHf08V0qwfChHxJgl404ag7BCnxntM3aKq33FijSae8ts2EaP79OusLN+aJNmUXLfzYCT/UaE4POgzmDCHf6VrKwIPVymQ7dhNaw2zUjaqM/DBlzKxc9KvQNvGU25QiyjgGmMxXp4TvNIxaR5rbw/Qo3n683dZ7CqQmaNEsTK9k90BxlPnw8A4tQA3L+yQbkrjzlaNRIkyfYDdLyJq1Fz3W8aWlolDSPs5J1ya7z3WMOLxvhcmyZRAWnsKaJhth1P47kriikHRIbk1wZslfyi6PrG0sgpLIa5eiS8gIlbpZplE3h6i8lMvFa2Bi2qlGwU4QtaUDKPn3uE4ZL++p17oa4R1DZwJ3nbjC6ojjZBPa7zzLHAR21obaJ3mynkvIMZPfjBs8F7ZOUgNaeMqrH5LHOlY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.0.0
Release Details
UpdatedJune 28, 2026, 9:57 p.m.
Changelog

Versions 4.4.8 and 4.4.9 were internal migration-vehicle bumps and were never released; this entry covers everything since 4.4.7.

Added

  • Signed completion certificates. A course can define pass criteria (score threshold, validity period). When a learner passes, the app mints a cryptographically signed certificate — a W3C Verifiable Credential (Ed25519 / VC-JWT) bound to the learner and the course.
  • did:web issuer identity. Each instance is its own credential issuer via a path-based did:web derived from the server URL; the public key is published at the app's did.json. Issuer keys are generated with occ learning:cert:init-issuer, stored encrypted at rest (ICrypto), and can be rotated without invalidating previously issued certificates.
  • Public verification portal. Anyone can verify a certificate by its ID / QR code on a public page. Signature, key status, claim binding, revocation and expiry are checked as an AND-chain; unknown IDs leak no personal data. Course owners can revoke certificates.
  • Compliance report. Per-course completion/compliance report for instructors and admins (who passed, when, certificate status).
  • Certificate view. A printable, A4-ready certificate with QR code, a prominent Certificate ID and a verification link.

Changed

  • Avatars overhauled. The default assistant Nova is now a full chibi character with emotion-aware expressions (neutral / happy / sad / surprised / sleep / celebrate) instead of an abstract shape. The three scholar skins (Theoretiker / Kosmologe / Astrophysik-Popularisierer) were polished — gradient shading, larger eyes, brighter signature accents.

Setup

  • Certificates need a one-time key. Run occ learning:cert:init-issuer once per server to generate the issuer signing key. Until it exists, the admin settings page shows a reminder and no certificate is minted when a learner passes. See README → Installation → Certificates setup.

Database

  • Version009100 creates learning_cert_keys and learning_certificates; Version009200 adds revoked_at. Verified on PostgreSQL 16 and MariaDB (the credential_json / secret_key_enc TEXT columns are intentionally unindexed to avoid the utf8mb4 key-length limit).
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureQPVnxiwJELOU6fw/Mnt31lPo9vwC3rVQXMtlqzYe6bNyTaq9hE6cKJGl61stgSGJaokwCCZOK3xG9aMhVng80khs2OADvguhtH0u+75IQmVpVQBZs9F4Ksh+qjwoI/m/j7o37tSsNi8MT5AoZMGl5QztB3RIR6Ir6WXARaJ9LDLIAv01Ufh1Sh3QubLeR/qnRmi7aiTdSixVy3ipiTPSP99IQ8whFyWml6E3E78Yzn1hdE269W013YjN38xnAZSP34LAU2I2NKCwne9d5v3fIhu2Ky0zlMlpNWY1MAhEBMUZcRYAcT3HdVmGq6xnW783+PDJO2CMAvsWFHmrGAW+3XkaduHK0jPmLfBw7WygDnFnxZTmOERiocYQ9WPmb5JAjFsZ/1+C5iGL6ZY9k4Bkr7oGCXYOCAqGYAYdBc7sY/s8QRa20+r5GO3CQKOOKEgIEFfF0ydDKOIWz3wQwEf9iL/Mz5RMgbzXgo797W30G1nViHoIgoPVnAipitPJxgvms20mhtB/V6HbqSGpGc9P2uIOoxLXQk7tZ8W0vhK+DG486S4Pmmk2Vw9fpZ5Wm7kAoNj1JYJtfO1OZ8mcpxX7hSPLZ7ChtpARjEJzCuB2pv5kYiYOiVHXvnZ5ZGPkT47A6TzVUm5Mc0Lk+rUadRvJiRT/4P2h7TvMSh+ofOTkBkk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 4.4.7
Release Details
UpdatedJune 24, 2026, 8:26 a.m.
Changelog

Fixed

  • learning_user_stats had no primary key (#24). Reported by @haecks on a fresh NC 33.0.5 install: Nextcloud logged "Table 'oc_learning_user_stats' has no primary key and therefore will not behave sane in clustered setups. This will throw an exception and not be installable in a future version of Nextcloud." The table was created back in Version001200 with only a unique index (learn_ustats_user_uniq) on user_id, never a primary key. Since there is exactly one stats row per user and every access path addresses the table by user_id, user_id is the natural primary key. A new migration (Version008000) drops the now-redundant unique index and promotes user_id to the primary key. The migration is idempotent (guards for missing table/column and an already-present primary key) so it is safe on existing installs and re-runs. Verified live on PostgreSQL 16 and independently on MariaDB 11.4 with utf8mb4 (the varchar(64) key stays well under InnoDB's index-length limit).

Fixed

  • v4.4.5 shipped without the data/ directory (starter pools, campaigns, simulator scenarios, privacy-info.json, PWA guide, course materials — 77 files). The release build followed a cp file-list that omitted data/, so on a clean v4.4.5 install the "Add starter pool" feature, campaigns, the practicum/simulator scenarios and the privacy page had no backing data. v4.4.6 restores data/ (verified: tarball file-set now matches the last-known-good v4.4.4 manifest). The i18n content of v4.4.5 (#21 + #19) is unchanged and included.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureftN/YerXrcUvkvumEZrPZISqpuiPEQ8z9LhPDuW05huisHlIYypdnqnbJbDtVB0cRS/4wHWWZqLS66JZbEYnaiyq5FzvS27ama++aIoPDUjCnbO1kT0dZKegpMuJbe4/EZIhMoRgfFGDd7j8Tz21tNa2oAA5ifka1JMgdZ3zyuGkNE4N3PHwszsZY0PYM3Vk/KOX/NKZJDQQY+B1xURqqULtDn+X5vO9uumUU4gyZTWY9x22Sf2rRg4Tt9MdKIkKo9AdCT18OPc0ymsQQU0Ijp01cw7M2hopuGvcnBUjYJV0ALDw1ChMJjXEjxMM8GK1bST+Ge9Mwc31vKIhb3NC54p58m3DmFdkL/+pSXSWCGWdIJ/hOBlrVDVssSVXpqaFAjL0k0owI6O+ZHD2cnIKX0Nc2tUblKgMZmEtX75TLeZ8wENMYTcOVe56+9jCYarHKVEsL1Nx0i4XbqoN/6OTXSyrVbxG3barZL58DCO6Nla+kUFjILEsbV88eIhl+2EYuidefDeIbr4SMNYU+JabbgFj9/4QO8lYuoah7r4ox/RHvKhM8koNabXuPngLv3XbQY4rb7qJqfrkbSzFJuHCGPUlgZLWfGIfEf+euCrkwWlQdjFjQhFh73gHveHttfc6ZpOUttCgza6qSZ7GliplIxEuYGjC6BxEKEB/5mka84g=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 33

Learning 5.4.0
Release Details
UpdatedAug. 24, 2026, 4:05 a.m.
Changelog

Prompted by a report from a system administrator piloting Learning at a manufacturing company in Vinnytsia, Ukraine (#3), who raised two problems: parts of the interface appear only in English or German, and there is no manual. Both turned out to be real defects, and the first one was considerably larger than the report suggested.

Added

  • Ukrainian interface language (uk) — the sixth language, complete at 2,940 keys in the main catalogue plus 252 in the VirtuProf runtime catalogue. Plural form is the three-form Slavic rule, not the two-form default.
  • This is a first pass and has not been reviewed by a native speaker. It is labelled as such in the manual, with instructions for submitting corrections without touching code.
  • docs/MANUAL.md — a user and administrator manual covering the chain the reporter asked for: course → learning materials → pool → questions → students → test attempt → results → completion, plus admin setup, the occ commands, mandatory training and compliance, the AI consent model, and how to add or fix a translation.
  • Linked from README.md and from a new <documentation> element in info.xml. That element was absent, so the App Store listing offered no documentation link at all — the direct reason the reporter could not find any.
  • Two new i18n gates, both wired into check-i18n-parity.sh:
  • scripts/check-i18n-coverage.py compares the source against the catalogue. Gates 1–3 all compare the language files against each other, so a string missing from all of them at once passes every one of them. Verified by reconstructing the defect: removing a key from all six files leaves gates 1–3 green and turns this one red. It covers JS/Vue t()/n(), PHP $l->t() and the indirect toolCatalog.js keys.
  • scripts/check-i18n-placeholders.py asserts that every translated value carries the same placeholders ({n}, %1$s, …) as its German source, and that no value is written in the wrong script. With six languages and machine-assisted translation, a dropped placeholder became the likeliest remaining defect.

Fixed

  • 635 interface strings were absent from the translation catalogue entirely. They were passed through t() but had no entry in de.json, so Nextcloud's l10n fell back to the source string and rendered them in German in every language, including English.
  • 618 came from JS/Vue, 10 from PHP $l->t() calls (notification subjects, the legal notice and privacy links), and 7 from toolCatalog.js, which reaches t() through a variable and is invisible to any literal scan.
  • The most-used casualties: the course materials tab almost in full, parts of course administration, and the answer-type selector in the question form — Einzelauswahl / Mehrfachauswahl / Freitext, the three options every author picks between when writing a question.
  • All 635 are now in the catalogue and translated into all six languages.
  • VirtuProf keeps its own 252-key runtime catalogue (src/l10n/virtuprof-strings.js) which is separate from l10n/*.json and was not covered by any gate. A language added to the main catalogue alone would leave the assistant speaking German or English. It now carries a uk dictionary, and virtuprof-i18n.js lists it.
  • VirtuProf's backend language allow-list silently discarded valid choices. VirtuProfController::ALLOWED_INTERFACE_LANGUAGES did not contain fr, although the frontend has offered French all along; normalizeInterfaceLanguage() coerces anything unlisted to '' and returns no error, so a French user's choice was dropped without a trace. Both fr and uk are now listed, with a comment naming the three places that must stay in sync.
  • scripts/deploy-prod.sh synced a hard-coded five-language list. A new language would have been generated, guarded and committed — and then never copied to the server. Now six, alongside l10n_js_sync.py and check-i18n-parity.sh.

Known limits

  • Some strings still reach t() through variables that the coverage gate cannot enumerate (onboarding slide definitions, dashboard tiles, a few config objects). Run python3 scripts/check-i18n-coverage.py --list-indirect to see the remaining call sites. Those were not audited in this pass.
  • The content language layer — optional parallel translations of question text, held in learning_q_translations / learning_a_translations — still accepts only de, en, ru, ar. Adding uk means a DB migration against a CHECK constraint and a re-test on both PostgreSQL and MariaDB, so it is deliberately out of scope here. It is not a blocker for Ukrainian use: questions authored directly in Ukrainian are stored and displayed unchanged, because the base question row has no language constraint.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturefOjauQfAoTxzZhK43pVS3uOvGA7B9xGJSgoX542rNXSSR5ofRPlwyghfUAWDO204V4VIxT68tuEawloJMFAYYs3FnBjPkCG4oFn/yLxRRrnsKasZD+igQe3RyfsLFOR0xR0MmUlvBYVeQxmSOZY+KDiHS7ED9FDgPMuHeOC5thDa6+dKZDzkBlNKmcLlIHk/Fop73pfVV8XDCS2VF/mwwt2/DxSc715n+KGoUQJ6InShXf9AYlB2ZralLwTPEBHocZvg910Xzt7Co34DkQT5dQmnzHj3uS8kKp+Jxou6U+lCnttxLTovxsmZnnmhF5kaHyonmTYa4QxzuXmeqTDRCHkd/Row7bnTy2MsAEqmS0M/KF/pAW6m++1mABO9Axe318aewrbPfZp3kei9FHohI+FAZMt0DQ3e5Bmp+RChArWS8ndS6aPrrFjA87zym4FvxXRbf8hfyYVvihBWHmefgEwXcbJZwwe6cOBjiQloin97i7AhuPh1YdscWSrEjZbkdZnjV0Vvl6BhKV2X3oAQIf6yySfKe60qw1F/gqz6fB4NG5ngDosDBjVcgT363g6rNng8brp89/g6gk7rN8+PyLttCyTxnzWcGVXdyujtFjS2frX47blARJOXhwD/PpmhSHJRsxE6e7vIk+HrgHqvkaDGuy/15WnKYu64AfeFauo=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.3.1
Release Details
UpdatedAug. 22, 2026, 9:29 a.m.
Changelog

Fixed

  • Every AI endpoint returned HTTP 500 instead of working (#3). Nextcloud derives the controller class from the route name, so 'name' => 'ai#available' resolves to OCA\Learning\Controller\AiController. The file was named AIController.php and declared class AIController; because PSR-4 autoloading is case-sensitive, the class was never found and the App Framework threw QueryNotFoundException before the request reached any of our code. All five ai#* routes — available, generate, status, import and explain — have been broken this way since they were introduced in v2.7.0.
  • Visible effect was mostly log noise: the callers in QuestionList.vue, LeitnerMode.vue and TrainingMode.vue swallow the failure and fall back to aiAvailable = false, so the AI buttons simply never appeared. Nothing was lost, but nothing worked either, and every page that asked wrote a stack trace to nextcloud.log.
  • The class-resolution failure happens before the authentication middleware, which is why the endpoint answered 500 to anonymous callers where every healthy route answers 401. That difference is what pinned the diagnosis.

  • Four SQL queries referenced columns that do not exist, each failing with SQLSTATE 42703 every time it ran. All four were invisible to PHPStan — the defect lives in a string — and each sat behind a catch (\Throwable) that turned the error into a 500 or an empty result.

  • LernprofilService: c.name on learning_courses, which has title. In enrichPoolsWithCourseData this broke /api/profile/skill-map outright (since 2026-04-09); the identical mistake in the cheat-sheet query hit only users who actually had wrong answers to report, so it stayed hidden behind an empty-result early return on fresh accounts.
  • RagContextService and CourseSummaryService: ua.user_id on learning_user_answers, which has no user column at all — the user hangs off the session. Both now join learning_sessions the way NoteGeneratorService already did. User weaknesses and trouble spots never loaded; they threw.
  • CourseSummaryService additionally compared the boolean is_correct against the integer 1, a type error on PostgreSQL. It now tests the column directly, which is portable across PostgreSQL and MariaDB.
  • NoteGeneratorService: chapter_ref on learning_pools, a column that never existed (the chapter columns are chapter_key / chapter_title / chapter_order). Every authorised POST /api/notes/generate died in this query, before reaching the model at all.
  • RagContextService: name on learning_courses. This is the first query buildContext() runs once a course is set, and a shared catch aborted the whole build after it — so VirtuProf answered without any course context (no chunks, pool questions, Leitner stats or weaknesses) and without reporting an error.
  • CourseSummaryService: winner_uid on learning_duel_sessions. That column only exists on the league tables; a duel's winner follows from the scores. This query is on the ordinary course-summary path, so it took summaries, snapshots, narratives and CSV exports down with it. Wins are now counted from the scores of finished duels — and so is the total, because a duel that is still waiting for an opponent or expired unplayed has no winner and would otherwise sit in the denominator of win_rate.
  • FeedController reported no course names. It called getName() on the course entity, which only has getTitle(); the surrounding catch turned that into course_name: null for every feed item rather than an error.
  • Skill map and cheat sheet exposed courses the user is not in. enrichPoolsWithCourseData accepted a user id and then ignored it, so it returned every course any accessible pool had been added to. An instructor can attach a pool that was merely edit-shared with them, which handed the pool's owner the id and title of a course they are not a member of. Both queries now apply the same membership test the rest of the service already used, and both order their results — a pool can belong to several courses and the mapping keeps the first row, which without an explicit order was whatever the database happened to return first.
  • LernprofilController logged nothing when it failed. All five catch (\Throwable) blocks returned a 500 without touching the logger, which is why nextcloud.log held no trace of an endpoint that had been broken since April 2026. They now log the exception before returning.

Security

  • Duels and gameshows could be started on any pool. DuelService::createDuel and GameshowService::createSession only checked pool access when a courseId was supplied, and both endpoints accept none — so two cooperating users could start a session on an arbitrary pool id and read its questions, its answer options, and after answering the correct answer id. TrainingService has always gated its equivalent path. Both now call the same check, which is made public on QuestionService rather than copied a sixth time. Verified live in both directions: a pool the caller cannot reach is refused, one they can reach still works.
  • The Wissensturm category endpoint had the same hole from a different angle: it took any pool id as the chosen category and read a question from it. It is now authorised against the session — the pool the session was created with, or any pool of its course — rather than against the caller's own pool access, because players who join a session legitimately are not required to have access to its pool.
  • POST /api/courses/{id}/summary/snapshot did not check course access. Every other endpoint in SummaryController passes through CourseService::findById() first; this one called the service directly, so any logged-in user could snapshot an arbitrary course id and read the result back through their own snapshot. It now applies the same gate and answers 403.

Added

  • scripts/check-sql-columns.py — resolves every alias-qualified column reference in app/lib against the live database schema. It found all four SQL defects above in one run.
  • ClassResolutionTest — walks both places where Nextcloud builds a class name from a string (appinfo/routes.php route names and the FQCNs in appinfo/info.xml) and asserts each one resolves to a file whose name matches exactly, plus that the file declares the class name the router will ask for. Neither PHPStan nor the controllers' own unit tests can catch this: those tests use the real class name, so they load and pass while HTTP is broken.

Changed

  • .githooks/pre-push now runs PHPUnit and the SQL column check. Neither the PHP test suite nor any schema check ran in a mandatory gate before, which is the reason both classes of defect survived for months. Note that the versioned hook was not the active one — core.hooksPath pointed at a stale copy in .git/hooks.
  • The API test for /api/profile/skill-map asserts 200 and a payload shape; it accepted 500 before, and so reported a hard SQL error as passing for four months.
  • The API test for /api/ai/available asserts 200 and a boolean payload. It previously accepted 200 or 500, which is what let a total outage ship through 23 tagged releases over five months — the endpoint always answers 200 with {available:bool}, including when no API key is configured, so a 500 was never an acceptable outcome.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYUSX7HQellMvxhF9ONH3kjJE27MJqyFeN7kD1SUBZ+IQWY1+4g0xdnfNr3eblyATtg5gdSTqi34Si6TuDz4lETH0/R3jDPG/UHGjtqHpclzdXhTd3diZh1BvpU+XQhHH86Q+vukW5NUOak+MzINOX3JMH7Q0hklzyvEu0K0DnTiPvnTnilMWGeJfIWbc/isBOb+P1GlHCiEzZY4/KvDN78aPNeNBva/j3aLX4V26OJeIeIRVx5PN22Cmi0k1HAH8D8oGGG8HoNYxDmEmUgoIgsBeqrU+Y0tLwtcmTo6K2bjepBRHMEngiUe7ytyKS/ty/in+BbVIapESWNlNplIi+JvxwI+pczWj5/rnowOSsBvl/vj27LxHZm/sEcdqjICp2jbuVTJA734MwuzlAbZG6+XbD5BR8iF6GqNxzfhHCtDQ6V7N4XGQ7yKaaYDAOVsFPwKTSXRaOq9nF+yPADZS/fBij3uNmHk8IAWQjUAO54DZup3KeMM9f2TVWP8la+aEEYoaxVKHUOWfjEqUQ1ptE9UDs5Z8otOLM8FR5WM5xnNQ+N6qSJi2UrK53OtqzSzh2VQ+xIrDDn6NdjeRqayGDwC18cOI5UWixKHoPoic0FYHuPJztK82zy+yFTLz7bfYNJBl73uNTDc46+GIbXfmLKTh7g4em0rncGu4aC1I/Pc=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.3.0
Release Details
UpdatedAug. 18, 2026, 3:30 p.m.
Changelog

Added

  • occ learning:uninstall — removes everything this app leaves in the database, so the app can be uninstalled cleanly (#1). Dry run by default; --execute is required to change anything.
  • Drops all learning_* tables, including the legacy names from both historical rename waves, and clears the rows left in migrations, appconfig, preferences, jobs, notifications, activity and activity_mq.
  • Deletes the metadata rows before dropping the tables. Leaving the oc_migrations rows behind makes a later reinstall skip every migration and boot broken, so if a run dies between the two phases, this order leaves the recoverable state.
  • Refuses to run when issuer/certificate data exists unless --keep-certificates or --drop-certificates is chosen: the Ed25519 issuer key is unrecoverable, and losing it makes every certificate ever issued permanently unverifiable.
  • Refuses to run when any table cannot be read. An unreadable table is never mistaken for an empty one — every guess here is destructive.
  • Reports rather than touches what it cannot safely change: the theming legal links (rewritten by Application::boot() on every request, so they can only be cleared after app:remove — the command prints the statement), the Dashboard layout row, and the files the app created under /Learning/ in users' homes.
  • Tables carrying the learning_ prefix that this version does not know about are reported, not dropped.
  • scripts/db-uninstall-probe.php — runs the real command against a throwaway MariaDB and PostgreSQL, covering platform behaviour no unit test or PostgreSQL-only dev instance can reach.

Fixed

  • occ learning:import-pool-json no longer drops valid open questions. The pre-check demanded at least two answers for every question, which is right for multiple choice but wrong for open (fill-in) questions — those carry exactly one model answer and were skipped silently. The check now mirrors what QuestionService::create already enforced.

Changed

  • The privacy and imprint pages name the operating company, with the registration details an imprint requires, instead of a private individual.

Notes

  • No repair step is used for uninstall. Nextcloud executes <repair-steps><uninstall> from AppManager::disableApp(), so it also fires when an admin merely disables the app — a drop there would let one mis-click destroy every course, pool and certificate. The reasoning, including why a marker-armed variant was rejected, is recorded in UninstallCommand's class docblock.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureBIvlmCYXMWVkuElwUaidAWzPW5t6ZTALeGz+aQJFxs45ncQXSDopk45g5fBrZVXjcjOs7KerH3YpBwu3kbOMMPQ2x79O92Mkqa3zWpW2mVUXIpUOpgVUaXsEJcDIoz3kTiyrD0ZOnvrYgpQYTPy1r35QhANfB89BwhUreOIoz2/L7EryXoAzAg3GCaHT3KRARBGGaCUBEQvRlVh9X1gGvQtcOwYcE7nO2JpdXjVRrE17foqNtVwgDRKD3+CJDWwuGDeEe3F8pIGLtaWCS6ZhzRNluj3kFwafqjL4h7wo/m7lCPeNvd/gwKWdQ/QV0ZPG2zdkQGgLwDXW4V3pypNOpXgvtVRA2JEFW3dvLbCKOWTn68ntmsfuy6+WaNyFOjwq60YE2h5yBaXCFe8cT0Cbjo1pOotrAM0LNDPMe0Jz8ImV5tDkgTnzXIaD/rQWeyLv5VOdYgT/EFya1RKpQd59QxF5i4aOHl6j+M2o+LKUAZV0fcmO2vlZ0v3W5+aB7K2+fQOi2nl0MQ9f8fo+AmAJcDSe0SrFbOy5u9TMbzOV+nKcJmyJLBsN7uRtFxhAA6Zm9dvcJL7iTlJjr1q/uPWHiv+nAPpD5JNezDcFQ4jqfERzhhZbvA7CdozszMTyrbpTWPJfNKqU9ekyWDIn/XpinHOH1IsBggg07OKAEvFxlFQ=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.2.1
Release Details
UpdatedJuly 4, 2026, 12:03 p.m.
Changelog

A whole-app security audit (8 review lanes) followed by an adversarial multi-model pre-live review. Every finding was verified against the actual code before fixing; fixes shipping to the public App Store were themselves re-reviewed. No functional/feature changes — hardening only.

Security — Access control (IDOR)

  • Course-scoped authorization on RAG import/list/delete/moderate endpoints (an instructor of course A could previously touch course B's shared RAG pool); course-membership gates on RAG contribution and on listing one's own contributions.
  • Answer-key exports (CSV/JSON) require export-level access (owner, edit-share, or the instructor of a course the pool is attached to) — not plain read access; enrolled students can no longer pull answer keys.
  • Course-access gates on document index/material-folder and course feed endpoints (previously cross-course content/PII could leak).

Security — Exam integrity (oracle prevention)

  • During an active exam, explanations, correct answers, PBQ solution config and translated explanations are withheld across all surfaces that emit question-derived data (training/exam payloads, translation endpoints, the spaced-repetition due-queue, and VirtuProf's RAG + client-supplied question context).
  • Starting an exam no longer auto-completes an existing exam by switching course context; correct PBQ answers now increment the exam score even when per-question feedback is suppressed.

Security — AI / GDPR

  • Per-user AI consent is now enforced on every user-triggered LLM path (VirtuProf chat/interview, course summary, AI question generation, note generation, AI explanations) — the admin ai_enabled toggle alone is no longer sufficient. The frontend catches the consent-required response and points the user to the in-app consent dialog.
  • Stored chat memory is NFKC-normalized and injection-filtered before re-entering the system prompt (a payload stripped on turn 1 could previously reappear on turn 2); the injection sanitizer now covers the same role-change / prompt-leak / jailbreak markers as the detector.
  • File-intent note generation and chat-memory compression now count against the shared AI rate limit.

Security — Data-layer robustness

  • Repair migration for course-feature table-name drift (curriculum scopes, question overrides, announcements) — cross-DB safe (PostgreSQL 16 / MariaDB / SQLite), identifier-quoted, fail-fast on platform-detection error, and it aborts rather than orphaning legacy data if a rename fails.
  • Prefix-agnostic insert ids (exam slots, announcements) instead of a hardcoded oc_ sequence name; entity model aligned with the shipped schema.

Database

  • Version009900: idempotent, data-preserving repair of legacy learning_curriculum_scopes / learning_q_overrides / learning_announcementslearning_course_*. Verified on PostgreSQL 16.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYfFmoFuitzdd9S0evt388jKj56ydtqz4AABUiKwRxBS8pLpwDBnc1PPdvxLLLB/81ngsy7JN/Sair3bFET8GvCrWppH/QOJZQzxqC8DMsQLET+eXEW11WQrh7U2ON29fd+ai9BPh0c66Mb2U0Kshh1VbQhqtTOJHkLx9eV1KoeaMHLdc4euDJvIG2XxSJOIh+uhNOdfkIORIXSHDHL/iuyKvMTdlsq36zQqz86QNVOUKv43V7uFbaGK0t58E7W3IKYP8dAByprTS0Uck8vHf08V0qwfChHxJgl404ag7BCnxntM3aKq33FijSae8ts2EaP79OusLN+aJNmUXLfzYCT/UaE4POgzmDCHf6VrKwIPVymQ7dhNaw2zUjaqM/DBlzKxc9KvQNvGU25QiyjgGmMxXp4TvNIxaR5rbw/Qo3n683dZ7CqQmaNEsTK9k90BxlPnw8A4tQA3L+yQbkrjzlaNRIkyfYDdLyJq1Fz3W8aWlolDSPs5J1ya7z3WMOLxvhcmyZRAWnsKaJhth1P47kriikHRIbk1wZslfyi6PrG0sgpLIa5eiS8gIlbpZplE3h6i8lMvFa2Bi2qlGwU4QtaUDKPn3uE4ZL++p17oa4R1DZwJ3nbjC6ojjZBPa7zzLHAR21obaJ3mynkvIMZPfjBs8F7ZOUgNaeMqrH5LHOlY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.0.0
Release Details
UpdatedJune 28, 2026, 9:57 p.m.
Changelog

Versions 4.4.8 and 4.4.9 were internal migration-vehicle bumps and were never released; this entry covers everything since 4.4.7.

Added

  • Signed completion certificates. A course can define pass criteria (score threshold, validity period). When a learner passes, the app mints a cryptographically signed certificate — a W3C Verifiable Credential (Ed25519 / VC-JWT) bound to the learner and the course.
  • did:web issuer identity. Each instance is its own credential issuer via a path-based did:web derived from the server URL; the public key is published at the app's did.json. Issuer keys are generated with occ learning:cert:init-issuer, stored encrypted at rest (ICrypto), and can be rotated without invalidating previously issued certificates.
  • Public verification portal. Anyone can verify a certificate by its ID / QR code on a public page. Signature, key status, claim binding, revocation and expiry are checked as an AND-chain; unknown IDs leak no personal data. Course owners can revoke certificates.
  • Compliance report. Per-course completion/compliance report for instructors and admins (who passed, when, certificate status).
  • Certificate view. A printable, A4-ready certificate with QR code, a prominent Certificate ID and a verification link.

Changed

  • Avatars overhauled. The default assistant Nova is now a full chibi character with emotion-aware expressions (neutral / happy / sad / surprised / sleep / celebrate) instead of an abstract shape. The three scholar skins (Theoretiker / Kosmologe / Astrophysik-Popularisierer) were polished — gradient shading, larger eyes, brighter signature accents.

Setup

  • Certificates need a one-time key. Run occ learning:cert:init-issuer once per server to generate the issuer signing key. Until it exists, the admin settings page shows a reminder and no certificate is minted when a learner passes. See README → Installation → Certificates setup.

Database

  • Version009100 creates learning_cert_keys and learning_certificates; Version009200 adds revoked_at. Verified on PostgreSQL 16 and MariaDB (the credential_json / secret_key_enc TEXT columns are intentionally unindexed to avoid the utf8mb4 key-length limit).
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureQPVnxiwJELOU6fw/Mnt31lPo9vwC3rVQXMtlqzYe6bNyTaq9hE6cKJGl61stgSGJaokwCCZOK3xG9aMhVng80khs2OADvguhtH0u+75IQmVpVQBZs9F4Ksh+qjwoI/m/j7o37tSsNi8MT5AoZMGl5QztB3RIR6Ir6WXARaJ9LDLIAv01Ufh1Sh3QubLeR/qnRmi7aiTdSixVy3ipiTPSP99IQ8whFyWml6E3E78Yzn1hdE269W013YjN38xnAZSP34LAU2I2NKCwne9d5v3fIhu2Ky0zlMlpNWY1MAhEBMUZcRYAcT3HdVmGq6xnW783+PDJO2CMAvsWFHmrGAW+3XkaduHK0jPmLfBw7WygDnFnxZTmOERiocYQ9WPmb5JAjFsZ/1+C5iGL6ZY9k4Bkr7oGCXYOCAqGYAYdBc7sY/s8QRa20+r5GO3CQKOOKEgIEFfF0ydDKOIWz3wQwEf9iL/Mz5RMgbzXgo797W30G1nViHoIgoPVnAipitPJxgvms20mhtB/V6HbqSGpGc9P2uIOoxLXQk7tZ8W0vhK+DG486S4Pmmk2Vw9fpZ5Wm7kAoNj1JYJtfO1OZ8mcpxX7hSPLZ7ChtpARjEJzCuB2pv5kYiYOiVHXvnZ5ZGPkT47A6TzVUm5Mc0Lk+rUadRvJiRT/4P2h7TvMSh+ofOTkBkk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 4.4.7
Release Details
UpdatedJune 24, 2026, 8:26 a.m.
Changelog

Fixed

  • learning_user_stats had no primary key (#24). Reported by @haecks on a fresh NC 33.0.5 install: Nextcloud logged "Table 'oc_learning_user_stats' has no primary key and therefore will not behave sane in clustered setups. This will throw an exception and not be installable in a future version of Nextcloud." The table was created back in Version001200 with only a unique index (learn_ustats_user_uniq) on user_id, never a primary key. Since there is exactly one stats row per user and every access path addresses the table by user_id, user_id is the natural primary key. A new migration (Version008000) drops the now-redundant unique index and promotes user_id to the primary key. The migration is idempotent (guards for missing table/column and an already-present primary key) so it is safe on existing installs and re-runs. Verified live on PostgreSQL 16 and independently on MariaDB 11.4 with utf8mb4 (the varchar(64) key stays well under InnoDB's index-length limit).

Fixed

  • v4.4.5 shipped without the data/ directory (starter pools, campaigns, simulator scenarios, privacy-info.json, PWA guide, course materials — 77 files). The release build followed a cp file-list that omitted data/, so on a clean v4.4.5 install the "Add starter pool" feature, campaigns, the practicum/simulator scenarios and the privacy page had no backing data. v4.4.6 restores data/ (verified: tarball file-set now matches the last-known-good v4.4.4 manifest). The i18n content of v4.4.5 (#21 + #19) is unchanged and included.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureftN/YerXrcUvkvumEZrPZISqpuiPEQ8z9LhPDuW05huisHlIYypdnqnbJbDtVB0cRS/4wHWWZqLS66JZbEYnaiyq5FzvS27ama++aIoPDUjCnbO1kT0dZKegpMuJbe4/EZIhMoRgfFGDd7j8Tz21tNa2oAA5ifka1JMgdZ3zyuGkNE4N3PHwszsZY0PYM3Vk/KOX/NKZJDQQY+B1xURqqULtDn+X5vO9uumUU4gyZTWY9x22Sf2rRg4Tt9MdKIkKo9AdCT18OPc0ymsQQU0Ijp01cw7M2hopuGvcnBUjYJV0ALDw1ChMJjXEjxMM8GK1bST+Ge9Mwc31vKIhb3NC54p58m3DmFdkL/+pSXSWCGWdIJ/hOBlrVDVssSVXpqaFAjL0k0owI6O+ZHD2cnIKX0Nc2tUblKgMZmEtX75TLeZ8wENMYTcOVe56+9jCYarHKVEsL1Nx0i4XbqoN/6OTXSyrVbxG3barZL58DCO6Nla+kUFjILEsbV88eIhl+2EYuidefDeIbr4SMNYU+JabbgFj9/4QO8lYuoah7r4ox/RHvKhM8koNabXuPngLv3XbQY4rb7qJqfrkbSzFJuHCGPUlgZLWfGIfEf+euCrkwWlQdjFjQhFh73gHveHttfc6ZpOUttCgza6qSZ7GliplIxEuYGjC6BxEKEB/5mka84g=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 31

Learning 3.6.0
Release Details
UpdatedJune 24, 2026, 9:22 a.m.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturejQ1Xdi636ODYsfoC8Sr3MU9s6K0a/PIOZvbo0ea4D/TpW/NiVKu49cZC8JLt8oFbyp2qVI27LVBXEplSF6V004H1NMqHdnC3zhqEbs/BzRH9CIKZW7Ouzs2Qj7obBiBiszYtamVpxRvxt3bfCiIfac9mn6yMJ2OvsqDohraIT5Mh84kyuW4ibPVun/8hX2/qndkvsQQvKnO+GL8UHRV1UhiSG/ezTdjZbinc0ZVU06oWXJFuP2YXpFPc+GHG8RMBP6N3KeqmK+rMZ6lKaZY5fl//pnr8AbASDKYK0MjWQFsqLrei9R/iQbsHqjlT3vT5o0Sssrw0m++5ysCJhTb0uEPgeLvg4DUOige/iCegy+NZFb+qgb7oezetZD8s/aWxZTR645y1MZS4va5kgdAVreT0rvHDDZvx9UuKr4smMvAxX1DEUKgzqP/AMellHcwb4BVE9pVwOENp5oKttpoJXKoH/Lnwu+o+1o5m4+JH/SHHGJBCmXYeehFscARZceC9lNYsBTHL+uKILOszxJXK0OkwrFCVCGTnXFfO0hn23gwjECPGP/zwB8OwlwedZXjaZkAMomIulAxghFIw5arSOoX85kv7bOlwerVJ7jfHs8GYIqBn3b+bWzVn5MQEumuUiKBi01WH7vbjn5Neuxi7YfSBkjJjeQFOFlGEmwFwcrs=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 3.4.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Changed — UX-Navigation Struktur

  • Instructor Tab Groups: 17 instructor tabs organized into 5 logical groups (Lernraum, Teilnehmer, Kommunikation, Wettbewerb, Verwaltung) with visual separators.
  • Abenteuer Standalone: Adventure mode promoted from Arena sub-mode to its own CourseDetail tab, independently gatable via mode_config.
  • Arena Submode Gating: Each Arena mode (Duel, Gameshow, Oldschool) individually hideable per course rules. Arena tab disappears when all sub-modes disabled.

Changed — Code-Hygiene & Settings

  • Settings Split: Instructors now see two sub-tabs in Settings: "Kurs-Verwaltung" (admin) and "Meine Einstellungen" (personal). Students see only personal settings.
  • Zeitreise Removed: Removed 1270+ lines of dead Zeitreise/HackThroughTime frontend code (component, characters, navigation, mode_config key).
  • German Labels: All UI-visible labels now go through t() translation. ModeIdentityBanner labels localized.

Added — Simulator-Praxis-Sessions

  • Practicum Engine: Pure-JS state machine for guided session management with localStorage persistence and browser-reload recovery.
  • 11 Practicum Sessions: Real-world IT scenarios across all 7 simulators (42 steps total). Firewall, DNS, and Routing get 2 sessions each.
  • PracticumRunner UI: New "Praxis" tab in every simulator with step-by-step instructions, context explanations, progress bar ("Schritt X von Y"), and score summary.

Added — Student Dashboard

  • Heute Screen: New default landing page for students with SmartQueue widget (due cards count + "Jetzt lernen"), Daily Challenge card, streak display, and daily progress.
  • Global Feed: Aggregates announcements from all enrolled courses chronologically with course-name badges and pagination.
  • Pools Navigation: Direct "Pools" tab in student main navigation — one click to PoolList.

Added — DevCloud Integration & Leitner

  • Talk Room Link: Instructors can set a Talk room token per course. Clickable link appears in course header, opens NC Talk in new tab.
  • Materials for Students: Students now see the Materialien tab (read-only) when a course has a material folder set.
  • Buddy Matching: New "Lernpartner" tab shows who can help with your topics and whom you can help, based on Telos help_offer/help_wanted data.
  • Course-Aware Tools: Werkzeuge tab filters simulators based on active course's enabled_tools. No course active = all tools visible.
  • Sprint Intervals: Instructors can activate sprint mode per course (4h/12h/1d/2d instead of 1d/3d/7d/14d Leitner intervals) for intensive courses.

Fixed

  • Badge Duplicate Crash: Session completion no longer fails with 400 when badge already exists. Fixed catch to use OCP\DB\Exception (NC 30 wraps Doctrine exceptions).
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
Signaturely5UiDEZdUJh6uSRREEijl3s87EIDnAeze3Y9KBfIECnaFvdafIwluTTl1XeyEvAaGshOHIXMmtfVvnYA9dMLyIj+jKvSeWlFKgbTsMS7hRvCiuqGcoFBPuOm8rDzIk6AXTGieQFgNIho9p68tRzQ2MF1mFJ/qnd3ayGiasJtT7W7rbUQYNAtzL3n4CpmxVZi4msMamM+K7c0O7e6/pmQJ3HCmtpjurPs8MJzAJQf5dyTQQuDa44T9aOSi4ucDAnS+aETU20UH0C8HWU15un+T0FeIP8//J8VxHFkP4g6BzrHF9mA52QKgUnUTkxYcH1qVhL7TcKjvfGScG3MjnSV92hvpVlEDZhMNAGkCwORAPMG22r4moEOmLse0r4/tMkSZ/4InV5kc3NYmZFzigT3yBnZGmsTxyvVb1PdAGi0uNhIvS2Ay8nq6tARMz1lFMv3iz8y9MmwSwIsyMGIUq5xWG49/Fj9rqB6HvEj7lmojVhuGm4eBEiJ6dnuyu91l1O5sML68xbiaHk1lv+Q4Qy6THYKX5MKFcCTbFxfs+4acZRgquRLpLFDGkXtpJMs4XMjSCygcdKQy2bk6TSnncyhPFUiMCDkhavR/QICmtQw/RBJatqdf9+f6LT87zhA4g2jO9XXq6EcoaSTn0k7aS3eghXGRHumpYSNCDMXxuPnjI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 3.0.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added — Story RPG "Abenteuer" Mode

  • Story Engine: Full campaign-based RPG learning mode with branching narratives, skill checks, and character classes (Architect, Security, SysAdmin, Helpdesk). 20 campaigns across CompTIA Network+, Security+, CySA+, Linux+, and A+.
  • KI-Erzähler: Gemini-powered dynamic narrator with freetext player actions, NPC dialog generation, and role-based prompts. Fallback to static text when AI unavailable.
  • Skill Checks: In-story quiz phases with 3 questions per check, batch submission, pass/fail branching to different scenes.
  • 20 Campaigns: Real-world IT incident scenarios — SolarWinds, WannaCry, Log4Shell, Colonial Pipeline, Equifax, plus 6 Security+ exam-oriented campaigns (Phishing, Zero Trust, Crypto, Compliance, IR, Cloud) and an AI Security campaign.
  • Coop Mode: Infrastructure for multiplayer campaign sessions (UI placeholder, backend ready).

Added — "Zeitreise" Hack Through Time

  • 7 Epoch Campaign System: Phone Phreaking (1960s), WarGames (1980s), The Worm (1990s), Bobby Tables (2000s), Shadow Brokers (2010s), Supply Chain (2020s), Quantum Dawn (Future).
  • Period-Accurate CSS Themes: Each epoch has its own visual theme (terminal-green, amber-on-black, retro-web, etc.).
  • CHRONOS Guide: AI narrator character that guides players through hacking history.
  • Museum Facts: Historical sidebars with real dates, events, and technical details.
  • Character Classes: Epoch-specific roles (Phreaker, Hacker, Script Kiddie, Analyst, etc.).

Added — Visual Identity System

  • Design Tokens: CSS custom property layer (--lnc-*) with dark/light scopes and motion utilities.
  • 13 Character Registry: SVG silhouette avatars with emotion states (idle, celebrate, alert) for all NPCs.
  • Campaign Intro Animation: Full-screen intro with title, difficulty badge, and character reveal.
  • Dialogue Stage: NPC dialog component with portrait, speech bubble, and emotion tags.
  • Paper & Circuits Narrative Skin: Dark theme for adventure mode with subtle circuit-board patterns.

Added — RAG & Document Intelligence (v4.1)

  • Document Upload & Extraction: Instructors can link Nextcloud folders as course material, scan for files, and extract text from PDF/Markdown.
  • Chunking Pipeline: Automatic text chunking with keyword extraction for semantic search.
  • Multi-Source RAG Context: VirtuProf answers now cite course materials with [Quelle: filename, Kap. X] format.
  • 4000-Token Budget: Smart context window management prioritizing relevant chunks.

Added — Oldschool Board Games (v5.0)

  • Lernwürfel: "Mensch ärgere dich nicht" inspired board game with dice rolls and question challenges.
  • Wissensturm: Trivial Pursuit tower game with category-based progression.

Added — Personal Learning Bot (v4.0)

  • GeminiService: 5-layer security stack (API key isolation, input sanitization, output filtering, rate limiting, audit logging).
  • VirtuProf Chat: AI-powered learning assistant with chat-first UI, language auto-detection, and chat memory.
  • Note Generator: Gemini-based topic summaries saved to Nextcloud Files.
  • Lernprofil: Passive user learning profile with weekly study plan generation.
  • Auto-Triggers: Exam auto-notes, wrong-answer threshold alerts, weekly plan background job.
  • Ticket Triage: AI-powered support ticket classification and draft FAQ responses.
  • AI Consent: Opt-in consent overlay before first AI interaction, GDPR-compliant.

Added — Tools

  • Subnet Calculator: Browser-based IPv4 subnet calculator with CIDR/mask input, binary display, and VLSM planner.
  • Subnetting Question Pool: 50+ subnet calculation practice questions.

Added — Quality & Testing

  • ESLint: Vue/JS linting with no-v-html error rule, 0 errors enforced.
  • Pre-Push Hook: 4-gate quality check (Security scan, ESLint, Vitest 67 tests, PHPStan Level 5).
  • Playwright Browser Tests: 67 automated checks covering navigation, campaigns, time travel, tools, VirtuProf, security. 52 PASS / 11 FAIL / 4 SKIP.
  • PHPUnit: 12 service-level unit tests (Training, Leitner, Analytics, Course).
  • Test Strategy: 4-gate pyramid documented in CLAUDE.md.

Fixed

  • Campaign skill-checks now always use pre-loaded questions from scene response (no separate API call).
  • Campaign start always creates fresh session (no stale resume from previous play).
  • Missing Abenteuer/Oldschool/Zeitreise toggles in course mode settings.
  • WannaCry campaign: corrected from email dropper to SMB worm narrative.
  • PBQ subtype auto-detection from config structure when pbq_subtype not set.
  • Empty skill-check pools handled gracefully (skip to next scene instead of crash).
  • Narrative garbage fallback when Gemini returns malformed text.
  • AI rate-limits on VirtuProf available()/status() endpoints.
  • ESLint: empty catch blocks, v-if with v-for separation, escaped quotes in templates.

Security

  • Gemini language mirroring prevention (no prompt echo).
  • Story mode injection guards on freetext actions.
  • Story mode rate-limits (10 starts/min, 60 scenes/min).
  • Pre-push security scan for hardcoded secrets.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureMWUO0JmVVcGH0NNuBs36k/SJ6luN5fkUQPaxu+CxY+5FdW/X4uAAD6NgGuM/jQsqSyKsns4V1UgbtV15xhOlHyO8/fOrnIeU1tifv52U+Sud8OdnMeCy9eQ8TmbbZwrsxT7er+1hmRgrJPC+b1++4bWDE57D6tJ85xAeJ4TeNMkSQDAy8PGI7PnhG1ae0Xx4DAXAjIm6+GZqeletoRknfiLPpLyUcF2EMOqOxdWjXhDVEGhXMLgPUcJnLPi6avTjN/CCEK1awEzVr4IhaUUI86Dub/68KQmeOFZRusIARkqmY5Bq0ER9xzcWKf26pyl10xr1GJUh2sQB+Utw3cszgq6VPS8KdsVJ3a15+uqdinFxo8QHhJz409tF9RdQnqye+0WJ2qa4d4PbndPeAiAcu3z27tlmVDmwouvxrYco4wDgUSIW3lzayLjR8IiONHylfIZ56idwu/EAYEU2u9/0oKVDeGi+Izt6z4/aX9iTjuV29T1uB1e2aasUIWyRttCFBZ01PIaJqMfubHMvRmDrcjb9U4OKUTqLg58u29eeQMimHB9O3ij0n6zQn2MJdxnnahO5seMg5tuLwRETHbcxQ0yNLskSLvSDgIa8vBr/leUgUtBPUCbIx4msuV3Tuy7HpA9Lg+3C7ZkqOIne6k0vakIc4KPjAPL/SLBwZusxfDI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.6.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added

  • Calendar Token: Per-user ICS subscription token for external calendar apps. GET /api/v1/user/calendar-token returns token + subscription URL. POST /api/v1/user/calendar-token/regenerate invalidates old token. Token stored in NC preferences (never expires until regenerated).
  • Public ICS Feed: GET /api/v1/calendar/{token}.ics — unauthenticated ICS endpoint for calendar subscriptions. Uses token-to-user reverse lookup via oc_preferences.
  • Calendar Sync in Personal Settings: ICS URL field with one-click copy and regenerate button.
  • ICS UID domain fix: UID fields now use actual NC hostname (parse_url(getBaseUrl(), PHP_URL_HOST)) instead of literal @nextcloud.
  • AI Explain button: POST /api/ai/explain schedules a text2text task explaining why an answer is correct/incorrect. "💡 Explain this" button appears after answering in LeitnerMode and TrainingMode (only shown when AI is available).
  • At-Risk CSV Export: GET /api/courses/{courseId}/at-risk/export/csv — downloads at-risk student list as CSV (Name, Risk Level, Risk Reasons, Accuracy, Last Active). Export button in course progress tab.
  • Batch Pool Assignment: Add-pool modal in CourseDetail now supports multi-select (checkboxes) — select multiple pools and add them all with one click.

Changed

  • ExportController: Replaced anonymous class with DataDownloadResponse for ICS response (cleaner code, no custom render() override needed). ICS body extracted to buildIcsBody() — shared by authenticated and token-based endpoints.
  • AIService::getTaskStatus() now includes output (raw text) in status response alongside questions array — needed for explain results.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturegpWcIhT4IeeSqVyKoOjXGch+flHYM9TICkDbyl7oRxuCOBH6sfYOwu5upyqwzGKl05TIRX2bqIrJiNj+ULif8rORhf3hvt03mhVuDCU1RuH9AbsD9VNvyw5Rf69FDXWD/44jH4JBjQHhD0E9MoY/Kud6Cog6W7SmIXEmGlHNz7mbopa2OoZZws6AA+SwjkDvK9BbvDrae3brEkSc2udTdi2HqSa8tbKPouwE3vdEdHWJWF3/j7P+4GM5cDXIT0B4g9eyL8r8Q1AKO0Xu/1pGPddNmeDz4gK4cQFGJF1KhhgAMEA7EM9/7wkMW6hrONfD57PJywCdeJhMTGVgGbjzLRreJ57GuI4jPhjqumOsiXRS3Evyn5LmO/q7FXn54hl1VBZBb7Kdj/t46wZzeV+vkKP/TFu2a/Amr99KFDWnNhKaF0eg/xYBAyGdNE162L7lHjXUlu5o6d1iO8i8y1NQoexyta7cqo019m4/BjhAr1QWAo5QsFJQ6hD7t8oA6Es50yFIi39Vzn2vFrkNuoUkrGtUNUH5mxEoDTCaHeLKBokStXztfaXsZzRBsi7Eem70Hqh65PQ4GAA7/BIKksrGR9XXRbbQG6ZBa1NN2iZaPDNF6t+zbq9EcfpITq7aLOthuU2OEkW4zm2Ds6wrxE+gFU+fxCePWR5H7g3OJuOcwjk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.5.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added

  • Daily missions: mission progress model plus claim endpoint (GET /api/v1/missions, POST /api/v1/missions/{missionKey}/claim).
  • Mission XP claims table: Migration Version002000 adds learning_user_mission_claims with one-claim-per-mission-per-day guarantee.
  • Streak freeze tokens: weekly reset token support in streak calculation (streak_freeze_tokens, last_freeze_reset_week in learning_user_stats).
  • Badge tiers: new bronze/silver/gold/platinum tiers for session count, mastery count, and streak milestones.
  • Analytics UI updates: Daily mission cards with claim action and freeze-token visibility.

Changed

  • Streak handling: streak computation can bridge one missed day when freeze token is available; token is consumed on activity-side calls.
  • App version: bumped to 2.5.0.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureBtgrr+hdTfM0f4h9yPsjjpUL18GMWeoMLCyfuSk+f0aj+R2D8WpUaWos8YBBAdFtoTYGKeykyLd+Ww/8ckGusCAhrB8BiV2IDSKhna3EreyedyjQ7tI9KXrif6dvkN/8FQB3JsWSXyLujapZYGX0lsvVxwf6rzmLIsOhC5gAGD+aCHwsZDxzi2bWVr3iyMaQq1rqd7HxGMUZq6E67lRr2bKCXTK46QEX1TnGw/jtv7gMg5Kq0pG39oRgOTtpjNc/0VJR/KUw4PujcVYMaOyLOlJlNjN0bZjgsgyijcACSi5L3gF8/M7yKvHFwZK/MKaWozRsTu1fJBtvrblhCBeJvoAGanAW+1gCE4SyNzClU/ToY/utQGJWUFNhARuMHjmqkAiFjfoev57XvvKIlC3mBF9U8oRIL9uQY+O6D1d1oynxvyVLvCmMOzw36xzHcEr7Hygu6PJUEOS79QkROIBiivsT91fAgRyFnStnbrM+0Az8Jiyis1mRU3lzOOSEIomZoapMevwMgngBQUwfUPDeQysmaz5DdpmDNW6oHbjV8WwIItNkK2sZm4VoDMud+PyGbLBIG/3HBjQVS1Hrhc4cjpoBgVLASsIVh6qIzQAsJnpagFsxxAm9azNMiNm1vgvcqRUnVStk0hi4B4pO3pr76/K/h3m7Y7MJDu0n736r4Ww=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.2.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Fixed

  • Course Pool Access: Course members and instructors can now access pools assigned to their courses. Previously only pool owners and explicitly shared users had access, causing "Pool not found" errors for enrolled students.

Changed

  • PoolService: Extracted findPoolRow() helper, added hasCoursePoolAccess() fallback in find() method
  • 5 Services updated: LeitnerService, PoolService, QuestionService, TrainingService, TranslationService — all hasPoolAccess() methods now check course membership as a third access path

Technical

  • 5 changed files, 165 insertions, 12 deletions
  • No database migration needed — uses existing learning_course_pools and learning_course_members tables
  • Course pool access grants read-only permission (permission: 'read')
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturerHTnxsAiUtmAEk1HAE6AJN6jKvIyjYYyh58gOmB0JoBHJQyT5pFGKLy2mbGZ5ILVw/MqY1uD4/04K0b42tuj41OEKmXtIa53QcI2sBK0W3EeyAui2QmwXWlDUbaWYyo2BMarLHNt8WYuGcXM1erNLm0ucjsCAhCaW4QWwPeYU2j03TmyIhoi4MdUXXls6E0xJvLx3X8d/+lAJ5+JoFGXPkiK0celrDKW2jfiJP8t/GHWlutfA7HMmM6rzk9E+BgYMJgDLMZb93TVAs8TrTQDxQTjhN7zbWAd4l/QP1bbczF/tS8/wTW04yPJFoTr5HnNEHvsaPyB0oLmvvffxGmiwtKmyfTgf9vKBYARQGaj+khYGm2mKHTk5b4u1neqHJ92iO8CMPkj9ieg6AcZc2YV19URlTZMKbzn4lSxTfb4K5Gm+SXH/1heBs88fRNLaf5olKjuDiDExfxKlzzj+wiigXP9UF+va8sXj8JOz2TFMLQoPbTmnU1FOHgbtNJBcnnqe4SXz1eqRNSXQ2OGZW8bAmpo0oe4MK1CljeIIpIc+E5wjFZblWjw0NzIjoZEAkHg5SuHknsMDbcUqQPvCBJavpHM0L535Zmqpteh7H9kUw3H1BcD5hJyuAlL7FNKEtMO5TSOh7vFlCCNORllnXhi/i1oUL56gWGTbRUgiZs+fhE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.1.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • Free Text Questions: New open question type — users type free-text answers instead of selecting from choices. Fuzzy matching (case-insensitive, Levenshtein distance ≤2 for short answers, substring matching for long answers)
  • CSV/JSON Export: Download question pools as CSV or JSON files. Roundtrip-compatible with import — export then re-import produces identical questions
  • ExportController.php: New controller with exportCsv() and exportJson() endpoints, DataDownloadResponse for browser download
  • XP Streak Multipliers: Tier-based XP bonuses replace old linear formula — 1.5x at 3-day streak, 2x at 7-day, 3x at 30-day streak
  • XP Multiplier Badge: Visible multiplier indicator (1.5x / 2x / 3x) in Pool List when streak bonus is active
  • Open Question Import: CSV format question,model_answer,open and JSON "type": "open" with single model answer
  • Open Question in all modes: Training, Leitner, Smart Queue, Exam (textarea + batch submit), Daily Challenge — SwipeMode filters out open questions

Changed

  • XpService: getStreakMultiplier() and applyMultiplier() replace linear 1.0 + (streak * 0.01) formula
  • LeitnerService: All XP awards (correct answer, mastery bonus, daily goal) now use streak multiplier via XpService
  • UserStateController: state() response includes xp_multiplier field; answerChallenge() applies streak multiplier to challenge XP
  • QuestionForm.vue: New "Free text" answer type option with model answer textarea
  • QuestionList.vue: "Freitext" badge for open questions, model answer display, Export CSV/JSON dropdown
  • ImportDialog.vue: Help text updated with open-question format examples for both CSV and JSON
  • SwipeMode.vue: Filters out open questions (not compatible with swipe/tap interaction)
  • routes.php: 2 new export routes (71 total)

Technical

  • 1 new file (ExportController.php), 22 changed files
  • No database migration needed — question_type VARCHAR(20) already supports open, answer_ids TEXT stores JSON
  • QuestionService::isOpenAnswerCorrect() static helper used by TrainingService, LeitnerService, and UserStateController
  • Rate limit on export endpoints: 10 requests per 60 seconds
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureHyyUtHF7C32M+YZc15dn2kRI/DltaewXzt5Im3Zu3rPS12Q+ICmOK+AhT/TicLBFR3Zr21BjtyhcjcrFf5JmXIG9c65OEEyK2ApbXAa5HfFFhUbPIAjj+z2eG3F2+71DrFluPVg5imtKCJ1+zmZfNC3EFI+UFhUFPGor76KTJphQrT01qMaD21XuWlbr59lOI3PLuV+qENWBC2pvQMcBDTyfDEpanPR/NRZ9I31TqA5qj9d6su9UYxW0AO/21Dz6cTvUx6AeJksLyRjQsGEMZhvp5Lv3k3X4fiBp4q6j/VKtt/R89eCbNAjEcubZwYsAx6TA6gK1FtfOwO++7pU4EPzDlKFTBUpqoXJFvelF+SRg5h5oNJYU4zoJRCRV7IOIwliR//2G4Rh5bKenEELEhJ47l8lHfdw2vAkY1zMcOFRoTmda27VPD4W+uOBnd+7sXUSlPLCPRZYmZTMuFehgnPvLM8jN7e57ldBnzYw2AfSkS8vFmsIiT/TqhUknc7kFUAMD3Cz0PmofWWM+CbglCnD/HURBJW6FYX8dnA7fJhkyi7wSeC9nFc9TAU3jlVRiL/RA/wAmlxP2ZXJUk4JpuzxsdAwuBXkDRgnrmjnz31WdiqAI9f8HOPh9vc9ttbbMXP35llqMTa3T9NfnMZR0pkY0SKXcmSbAK3IHsv4dPI0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.0.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • Role-Based Navigation: Students start at Courses view (no Pools tab), instructors keep both tabs. App title simplified to "Lernen" for students
  • Onboarding Hint System: 7 dismissible info cards (localStorage-based) explaining Smart Queue, Leitner boxes, Daily Goal, Daily Challenge, and welcome messages for both roles
  • hintMixin.js: Reusable Vue mixin for localStorage-backed hint dismiss/check across all components
  • Mode Descriptions: One-line explanation shown below the active mode selector (Training, Leitner, Wahr/Falsch, Exam, Stats, Manage)
  • Pool-from-Course Navigation: "Back to Course" button when opening a pool from course context (instead of losing context by switching to Pools view)

Changed

  • App.vue: Mode selector filtered by role — students see only Train/Leitner/Wahr-Falsch/Exam (no Stats, no Manage)
  • PoolList.vue: Create Pool button, Share/Edit/Delete actions hidden for students. Better empty state text
  • CourseList.vue: Student empty state now explains that the instructor enrolls them
  • LeitnerMode.vue: Box labels improved — "Neu — taeglich wiederholen", "Lernphase — nach 1 Tag", "Bekannt — nach 3 Tagen", "Gut — nach 7 Tagen", "Gemeistert — nach 14 Tagen"
  • SmartQueue.vue: Better empty state ("Open a pool and start the Leitner system"), ready state shows priority sorting info
  • PoolList.vue: "Jetzt Lernen" → "Start learning", "Trouble Spots" → "Trouble spots" (consistency)
  • de.json: ~60 new German translation keys for all hints, mode descriptions, empty states, and v1.8/v1.9 UI strings
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureX22wHYsHapYtxGW+/4w3wuihpu0uy3Mi5x076ajJx5VbizQVWSF8uURw2bu+y/pQn4GZxinHK/JxHWqcCdU86MB3ThayzDX7ySTGx/z016eudjyAYAYGPx84KJ+ciGXFFiiS+QA7ebnPjnm/8O365ve4tVtv+7EqjHzOwntJnlT+GL9FAYiUS1z5UzFSuI02nNz3GXc3PG1NeWf3M1iAG9Q26zjv3n4q0mV1/GstjWvuJ5bAH/C8eNWtumTPjDq0/sBza15+DPzBtTuSDanieXSPi+EG2geKozRNe7fWBCWflVScnK38+9ke0vQsC+cgNMgsS+t9Qquv1IHQqbQLzEpFRq88LcWfar9VH0YFG9E1FaX2XrbF+59lZdM39NvwLrOCIfJFt7Qpnv/9Zkv4voezhaST8lKLI7FC/jCNhDcN6IpLPj3a8VuYsyMfu2vq3A1VDAyq7qb7T0l7T/FTRVQG2DlxZLnRMSMsQgFHmbiP2/5S553/N9JErAgrY85op8ucOCv83ysvUOYLVNtXHbPq9YyH+7GXDK0OsIvIfoK+gsaj/qcBt2x88FH1SlpY0VLDkZ7huBC21rvKTV0lT3sXyNo0H2HKPGR14Xm8DsN5GCIaljf0ErOJRfsxX9nUpDqU5aqWk8ABK+kyTldmwViOizIzc0uMMdeu0G7bUWE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.9.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • AI Question Generator: Paste text (lecture notes, textbook excerpts) and let AI generate multiple-choice questions. Three-step workflow: input → AI generation → editable preview → bulk import into pool
  • AIService.php: NC TaskProcessing API (NC 30+) with TextProcessing fallback (NC 29) — runtime detection, no app-level API key management
  • AIController.php: 4 new endpoints — GET /api/ai/available, POST /api/ai/generate, GET /api/ai/status/{taskId}, POST /api/ai/import/{taskId}
  • AIGenerator.vue: Full wizard with textarea input, question count slider (5-30), language selector, polling progress, editable preview with select/deselect all, inline editing of questions and answers
  • At-Risk Early Warning: Instructors see which students are falling behind on the Course Progress tab. Rule-based risk scoring with 5 weighted signals (inactivity >7d, accuracy <50%, box-1 stall >60%, lost streak, <3 sessions in 14d)
  • At-Risk API: GET /api/courses/{courseId}/at-risk — returns students with risk_level (high/medium), risk_reasons array, last_active, accuracy
  • Remediation Queue: Focused practice on hardest questions (≥3 wrong answers, <30% accuracy, box ≤2). "Trouble Spots" button on Pool List with item count
  • Remediation API: GET /api/leitner/remediation (items), GET /api/leitner/remediation/count (lightweight count)
  • Daily Challenge: One random question per day from user's pools with +15 XP bonus for correct answers. Deterministic selection via crc32(userId + date) — same question per user per day
  • Daily Challenge API: GET /api/v1/daily-challenge, POST /api/v1/daily-challenge/answer
  • Migration V001600: Adds last_challenge_date (VARCHAR 10) and last_challenge_correct (BOOLEAN) to learning_user_stats

Changed

  • QuestionList.vue: "Generate with AI" button visible when AI provider is configured (checks /api/ai/available)
  • PoolList.vue: "Trouble Spots" button (orange, warning style) and inline Daily Challenge card with answer submission
  • SmartQueue.vue: New mode prop (queue | remediation) with conditional API endpoint, titles, and empty states
  • App.vue: Wires openRemediation event from PoolList to SmartQueue with mode="remediation", new smartQueueMode state
  • CourseDetail.vue: At-Risk section on Progress tab — collapsible card grid with risk badges and reason tags
  • routes.php: 9 new routes (ai: 4, leitner: 2, user_state: 2, course: 1) — 69 total

Security

  • AI Rate Limits: generate endpoint limited to 5/min, import limited to 10/min
  • AI Input Validation: Text length 50-50000 chars, word count capped at 3000 for LLM input
  • AI User Isolation: Task status checks verify userId matches task owner
  • At-Risk Access Control: Endpoint restricted to course instructors via validateInstructor()
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
Signaturedp2AlnFQwp1Cy8OCA/kE3gVqTvYhN9ORN/Oa86Lk0jjsYVLVFDHML+2ga4rlw5rjLTzojVtzB4BM1uJdyu/fRrdCUz9DhBBxIgNLNto8oIzu6HOYkE4TCB38quz5MGBbCVvZWnjmKb0ugYvzixvpy7ZPJnAWB5bwfj8C8AJqeEbbqqClkbjE4SAO9sxLfJbzDyWK7au4/It+H5gOO1yzXzUL6azX31M4acCwXZNI8AMRj6UHHqudtLsb9WgEC+cIHGEOfArbdIN9MDh1eDU3R17uuPjJw19g0KqZsc/NqGb/XuPUmHOHdcr8XzHDO4gYxp6FTEiMR8anCFogfEzpfrwGr/RAFzFBI0zfZ0+cbLHCNW3xrPVy/hOKGTlYXUHroalE6cvYdniSoXvQ09m6GlynsCauLZ8+hCb679ewlremqakW3E5RLtPL+xkP/ti0b8nev0Mdd9smil0YbrtPA6kJyk5JgllkPT3xhDDOtYrH+yrX2VNp5f4ypdWkPS8hM/SHId2W5q2D6K9XHVymde1aeA7AYPfa5/c62k7bvmwLbbNOWF4stKfq8MCA83RXASbfeHnAbdNbFltSPB9CT8oHBlBikwxk32iVKyoGEzuKZbAtZhC1x69ryyO0Uw5BAx6vexGDJUdgQb3PyeN5i2tV3Z2s3GCtHMjF+y0E8tY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.6.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • ConsistencyCheckJob: Daily background job that reconciles learning_user_stats against source-of-truth tables — self-healing for XP, level, sessions, mastered counts, and streaks
  • Composite Leitner indices: (user_id, box) and (user_id, next_review) on learning_leitner_items for faster aggregations and due-question queries (Migration Version001300)

Fixed

  • HIGH: LeitnerService::answerQuestion() now wraps box update, stats update, XP increment, and badge award in a single DB transaction — prevents partial writes on crashes
  • HIGH: Optimistic concurrency control on Leitner item update (WHERE box = :oldBox AND correct_count = :old AND incorrect_count = :old) — detects and rejects concurrent modifications including Box-5→5 lost updates
  • HIGH: Box-5 mastery bonus (+25 XP, +1 mastered, badge check) now only awarded on actual promotion (Box <5→5), not on repeated correct answers in Box 5 — prevents XP/stats inflation
  • HIGH: Badge notifications and Activity events now dispatched after transaction commit — prevents ghost notifications on rollback
  • MEDIUM: Demotion fallback (updateUserStats()) moved after Leitner item update — recalc now sees correct box value instead of overcounting
  • MEDIUM: syncLevel() deferred to after transaction commit — eliminates lock contention under concurrent reviews
  • MEDIUM: Cache invalidation moved after transaction commit (was inside write sequence, could serve stale data on rollback)
  • LOW: Multi-select answer validation uses batch IN query instead of N+1 per-answer queries

Performance

  • Leitner box aggregations (stats, mastery counts) use composite index instead of full table scan
  • Due-question queries benefit from (user_id, next_review) index — faster Leitner review loading
  • ConsistencyCheckJob processes max 500 users per run, oldest-updated first — all users eventually reconciled
  • ConsistencyCheckJob logs progress and catches per-user errors without aborting the batch
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureKQnhIvGaYFr9qdGYI0R0k4GLBar4u3swxmHkT+MfcE1phTVpyAm8O9s/s1TIz7qIDG3TA7CcjVDPTh57M2WysEi8/Yu8OZgE2pjJbsTOOssjRtFoGUqzyU4AR74azP5w3f/wdgk0jiIEQvOV+4aM3fENSOSktpfeGZzhch0xHUfON9kS2h4x8+8AqH9M/xkPJvS0dUIUoGNGYsug8qu3HkRcBxsH7n4hYzg3ycx+eL8vWpwfhFOhAV70cpcHOrK/oVz0ra4tl06LdPDNWeCB3AYCHU8hcehaPr+VsNJmgOdBoRf7JbesAu5YOB25qy340wJ+XlLonXe03NTq+xoG67Na0laU9cs+j56X856ik8G0UeT4vIywTObfh0iKmtqk/pUKzv0YopTYl/iSPtU/hTquoXnSYbEALoyPyh9aJpgpKXEn7d7qyoMo5/9Bx1owdXNxIi7gGtue1MMrFTkCIEm3/SMFWmA4XBXSRt2zP4sEUKaE1L8STzO57jFtpzt/WfC47ccCRktkLEkXDIIpwYwjHQYol7P/luO1OqUwJVefFnC52Fc4tJDrabMIbLqfOqwUPip0o18zcqsaNpTVyc6Mw+VLgO6MkP6lmdWfq0nvpBpKMu5e7cySJoT440xsvCBTWIIcfQyYcMJGxFscSondANcYYAg/ixhtChuTd5U=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.5.0
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Added

  • Consolidated User State API: GET /api/v1/user/state — single endpoint replaces 4 separate calls (XP, streak, badges, progress)
  • Denormalized learning_user_stats table: O(1) reads for XP, level, streak, sessions, mastered counts (Migration Version001200)
  • Background Notification Job: NotificationJob (TimedJob every 4h) replaces widget-triggered notifications
  • Activity-App Integration: Badge unlock events appear in Nextcloud Activity stream with toggle in Activity settings
  • Timezone-aware badges: Night Owl and Early Bird badges respect user's Nextcloud timezone setting
  • XpService: Dedicated service for XP calculation, session XP increment, level computation
  • Response caching: User state endpoint cached for 30s per user via ICacheFactory
  • Composite index on learning_sessions(user_id, completed_at) for faster XP/streak queries
  • time_limit_seconds column on learning_sessions for Speed Demon badge support

Changed

  • BadgeService refactored from 430 to ~200 lines — XP/streak methods extracted to XpService
  • Dashboard Widget is now pure read-only (~140 lines) — no longer sends notifications as side-effect
  • AnalyticsDashboard.vue: 4 API calls consolidated to 1, with fallback to legacy endpoints for rolling deploys
  • countUp.js: Respects prefers-reduced-motion — immediately shows final value without animation
  • BadgeUnlock.vue: Added aria-live="polite" region for screenreader announcements
  • LeitnerService: Box-5 promotion updates denormalized total_mastered counter
  • TrainingService: Session completion updates denormalized stats via XpService

Fixed

  • HIGH: Leitner XP now synced to learning_user_stats — correct answers award 5 XP, Box-5 mastery awards 25 XP bonus
  • HIGH: Race condition in stats UPSERT — all INSERT paths wrapped in UniqueConstraintViolationException catch with UPDATE retry
  • HIGH: SQL injection in incrementSessionXp$currentStreak now uses createNamedParameter() instead of string concatenation
  • HIGH: NotificationJob memory exhaustion — fetchAll() replaced with streaming fetch() loop; N+1 due-count queries replaced with single batch query
  • HIGH: Level-race in XpService — stale getTotalXpFromStats() read eliminated; syncLevel() is monotonic (AND current_level < :newLevel) to prevent concurrent stale-level overwrites
  • HIGH: Stats INSERT fallback uses updateUserStats() full recalc — preserves historical XP, sessions, and mastered counts for first-write users
  • HIGH: total_mastered demotion — Box 5→1 transition now decrements total_mastered counter with updateUserStats() fallback for missing stats rows
  • MEDIUM: User state cache invalidated on every Leitner answer (not just correct), after share-badge awards, and at END of completeSession() (after all badge/streak writes)
  • MEDIUM: Migration backfill is per-step idempotent — each INSERT...SELECT skips existing users via NOT IN clause (safe for partial re-runs)
  • MEDIUM: Migration backfill includes Leitner-only users (no completed sessions but have reviewed cards)
  • MEDIUM: Frontend fallback triggers on any error (not just 404) for rolling-deploy resilience
  • LOW: Removed dead $totalMastered variable in LeitnerService (computed but never used)

Performance

  • Dashboard load: 4 XHR → 1 XHR for gamification data
  • XP calculation: O(n) SQL aggregate → O(1) stats table read
  • Notifications: synchronous widget side-effect → async background job
  • NotificationJob: filters to users active in last 30 days (skips dormant accounts on large instances)
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureOyK9vuzcblU3s5znaUXHm8WvkECMxcl4Jz5Xt1pNg4mTSvtO0R26bat9p9AMH75j70QPKj0FUJYI6jhObMU7X/lUu6b6+UhvO+RCtzO3/4OQ+gwULA4qSX4oWzmiG6obxmI51H0AnBOCwLb/JuVlbJJzDWuaSetL7fZX5oOEGoNuom7ZK3eoPovLG0d94fhldGhXBph8D6iUsySsmxuA1nU/nFE8kFTXAj89xHxPb1V6G4omNw+0K7mfjshDC++A44/zibOE/XvHYhLPmch/MvvHyPkJ7jMXzhOsSJYB+EAQMGEVMig4tOf6Cug9HFd3+adiHIPP+UpyI3R1lZP5YNSMsuvDviDHXI16P25mr87PZO6/XcOxbwRV+eG7aiRlRTtgvgbfCPQacIJfT+zzZzGytY2sa+htxiTis0sdQY2BdsdtlwD75NmlP37KEZFJuG6y+4TTV7kN6iTueYxfoYTcQ9irz82FrgNYTAgWAgNXJ6K9rnlHlBAc7ZU9+VT6tNzi39eJflwwLJKPjhPyBKiZ10ycIPwgJYLAtbTQdaYF7rmBDVM8xkS+KSjvyErD+vzV+lZRVy6G2zHZpiRhPSFHGc1ytWa3o180ukeo0fw2/fz29R/OcIxFupinMuAPeaoHE2yrGkHvg8yJSHq2Scen7Bp1AhNmiL8BIbSAHe4=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.4.0
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Added

  • Achievements / Badges: 14 badges across 6 categories (sessions, performance, mastery, streak, social, fun)
  • XP & Levels: Experience points calculated from sessions, Leitner reviews, accuracy bonuses, and streak multiplier
  • Badge Unlock Overlay: Animated full-screen overlay with confetti when earning a badge
  • Analytics Dashboard: Achievements grid with earned/locked status, badge progress bars, XP level bar
  • Session Juice: Animated score count-up (countUp.js), Personal Best detection, improvement vs. average display
  • XP display: "+X XP" shown on all results screens (Training, Exam, Swipe, Leitner)
  • Nextcloud Notifications: Badge unlock notifications, streak-at-risk warnings, due card reminders
  • Badge progress tracking: API endpoint showing progress towards unearned badges
  • New DB table learning_user_badges (Migration Version001100)
  • 2 new API endpoints: GET /api/badges, GET /api/badges/progress

Changed

  • Training complete response now includes newly_earned_badges, xp_earned, is_personal_best, improvement, average_accuracy
  • Leitner answer response now includes newly_earned_badges when card reaches Box 5
  • Share creation now triggers badge check for "Sharing is Caring"
  • Streak endpoint now triggers streak badge checks
  • Dashboard widget sends streak warning and due reminder notifications
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYuFGppj5TQhlB5nd+fXqJurxGQUn7l0FFIqbWhXJMf9xPEi/g3wKdx7qukdK2ttz1Rf5xEbSdoLsGj3q1F7V5DZ+4+4GmMewS+MlYE4GA9ESFIA6lHLYxThICgb3G2lEGtImmp57tmGv7dzAiPqIH4pEdC0KT/iO08+VGy5R5s1gS5r36nHSrytf5C7JUBSSmOg6rVpUFIccDaXv8zn1f9sWWAZw4pRptDx+X29foqY2Zt4S/Swuz/SOGWKyfhhPw/x6RHjB3vnJIxx64Oi4Rom8BNdrU911RtMUFXLcnxq4xsv7VO2wGMkjNHegpL2yNr68yJSEm0bOjmVih06XMpJDNC0KN4S0vwnUkDIm1t9Dk1zxSPK5ba/PlQrTFxMVwratZsc1Uz0b/VqUBaZTu9oEBMpBm4CTrSf8XK29CNQ5EQXAzMSB0UUz0TqPpL2rI9kkcejJl0cololPd8N5R5jJpQ5miguwm6UjfTrIJzaizm6MAtXg+wx/DFfTyaSS936v1JEaRGIy4XkeQ9ZAAnSANlx9o8HTyLnWc7v6EsdkSo/7q88h7FaFUrlgkb3Gv7CQLBIJZ2jLtYa/2jv5gHQrg3c9unmIPLK8njpwS5+KgMH1V8LsyvraUW9ejzfmGv/BuN+reNyvToEoP8JIFPLs7SP1areV9svmkEnjIT8=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.3.4
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Security

  • CRITICAL: Close training-session oracle — startSession(exam) auto-completes open training sessions on same pool
  • CRITICAL: Defense-in-depth in submitAnswer/submitBatch — suppress correct_answer fields via cross-session check
  • HIGH: Strip explanation field from Question API during active exam to prevent indirect answer leakage
  • Rate-limits on ShareController and TranslationController write endpoints

Fixed

  • Leitner feedback shows hint when correct answers are suppressed during active exam
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureajCD73Wim+3CcebKFs72Co993wGojtfsPy8b+Gde+kx+EhPmDpzYEGdgbb54ZBqpDmJAroHIbaqZa0FfK8SwvFwC2JZ7WOp4xwOAjp+V7ruKvXu24MmzsH2wUp9U2ec+sO+EG9+ebsDELjaFEf2m6EMbRhw+/QUzsE51kaA10RaTwoNfTu29LLraGaEEFVWh9R5x5jbqNj9Wdvfq/vNNTp/XTm4SBLnAsD+7ymyMucfwHyn4ZYGspXdNd8v0HS6ZRe3JjtcOd+IjwEebZ4b2KPBCrnGs8sr+6zer7FItbLUOq2MxxdhaOml1yTQIPzeyuJPnFvmLJOw0FHJvsn/ZMMjCpByVSrf2MBua3yW4DcB/Z9m3yBwM3lQ98QUQDgmAfHwU1zG3P5ijvxFvaOz5XW3sFsCaktXfGtFfrMgUgbN8aee/7hZkA36Vi+zIUsbs+eExv5QvDn6bG21DrE8022vnfZ7gY9HFQESVMGsnZc9yqdeGJn9F09jScGcZvyinjtUUCe2/SRfjPHHa9JWvZ84pQuO+0OyAbvIEOlwz64r5qNatt47yoxAEzEUsu8oFQP2s+PX/Ni/yyEIWbH4LaK0qCPTulJKvujaq1fukSchT+9UMBVKxEebC672M1LtejtJnJfZVUp6WuAecz2QmBU4Va6WFDVLSkbAiz6ON/Jg=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 30

Learning 3.6.0
Release Details
UpdatedJune 24, 2026, 9:22 a.m.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturejQ1Xdi636ODYsfoC8Sr3MU9s6K0a/PIOZvbo0ea4D/TpW/NiVKu49cZC8JLt8oFbyp2qVI27LVBXEplSF6V004H1NMqHdnC3zhqEbs/BzRH9CIKZW7Ouzs2Qj7obBiBiszYtamVpxRvxt3bfCiIfac9mn6yMJ2OvsqDohraIT5Mh84kyuW4ibPVun/8hX2/qndkvsQQvKnO+GL8UHRV1UhiSG/ezTdjZbinc0ZVU06oWXJFuP2YXpFPc+GHG8RMBP6N3KeqmK+rMZ6lKaZY5fl//pnr8AbASDKYK0MjWQFsqLrei9R/iQbsHqjlT3vT5o0Sssrw0m++5ysCJhTb0uEPgeLvg4DUOige/iCegy+NZFb+qgb7oezetZD8s/aWxZTR645y1MZS4va5kgdAVreT0rvHDDZvx9UuKr4smMvAxX1DEUKgzqP/AMellHcwb4BVE9pVwOENp5oKttpoJXKoH/Lnwu+o+1o5m4+JH/SHHGJBCmXYeehFscARZceC9lNYsBTHL+uKILOszxJXK0OkwrFCVCGTnXFfO0hn23gwjECPGP/zwB8OwlwedZXjaZkAMomIulAxghFIw5arSOoX85kv7bOlwerVJ7jfHs8GYIqBn3b+bWzVn5MQEumuUiKBi01WH7vbjn5Neuxi7YfSBkjJjeQFOFlGEmwFwcrs=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 3.4.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Changed — UX-Navigation Struktur

  • Instructor Tab Groups: 17 instructor tabs organized into 5 logical groups (Lernraum, Teilnehmer, Kommunikation, Wettbewerb, Verwaltung) with visual separators.
  • Abenteuer Standalone: Adventure mode promoted from Arena sub-mode to its own CourseDetail tab, independently gatable via mode_config.
  • Arena Submode Gating: Each Arena mode (Duel, Gameshow, Oldschool) individually hideable per course rules. Arena tab disappears when all sub-modes disabled.

Changed — Code-Hygiene & Settings

  • Settings Split: Instructors now see two sub-tabs in Settings: "Kurs-Verwaltung" (admin) and "Meine Einstellungen" (personal). Students see only personal settings.
  • Zeitreise Removed: Removed 1270+ lines of dead Zeitreise/HackThroughTime frontend code (component, characters, navigation, mode_config key).
  • German Labels: All UI-visible labels now go through t() translation. ModeIdentityBanner labels localized.

Added — Simulator-Praxis-Sessions

  • Practicum Engine: Pure-JS state machine for guided session management with localStorage persistence and browser-reload recovery.
  • 11 Practicum Sessions: Real-world IT scenarios across all 7 simulators (42 steps total). Firewall, DNS, and Routing get 2 sessions each.
  • PracticumRunner UI: New "Praxis" tab in every simulator with step-by-step instructions, context explanations, progress bar ("Schritt X von Y"), and score summary.

Added — Student Dashboard

  • Heute Screen: New default landing page for students with SmartQueue widget (due cards count + "Jetzt lernen"), Daily Challenge card, streak display, and daily progress.
  • Global Feed: Aggregates announcements from all enrolled courses chronologically with course-name badges and pagination.
  • Pools Navigation: Direct "Pools" tab in student main navigation — one click to PoolList.

Added — DevCloud Integration & Leitner

  • Talk Room Link: Instructors can set a Talk room token per course. Clickable link appears in course header, opens NC Talk in new tab.
  • Materials for Students: Students now see the Materialien tab (read-only) when a course has a material folder set.
  • Buddy Matching: New "Lernpartner" tab shows who can help with your topics and whom you can help, based on Telos help_offer/help_wanted data.
  • Course-Aware Tools: Werkzeuge tab filters simulators based on active course's enabled_tools. No course active = all tools visible.
  • Sprint Intervals: Instructors can activate sprint mode per course (4h/12h/1d/2d instead of 1d/3d/7d/14d Leitner intervals) for intensive courses.

Fixed

  • Badge Duplicate Crash: Session completion no longer fails with 400 when badge already exists. Fixed catch to use OCP\DB\Exception (NC 30 wraps Doctrine exceptions).
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
Signaturely5UiDEZdUJh6uSRREEijl3s87EIDnAeze3Y9KBfIECnaFvdafIwluTTl1XeyEvAaGshOHIXMmtfVvnYA9dMLyIj+jKvSeWlFKgbTsMS7hRvCiuqGcoFBPuOm8rDzIk6AXTGieQFgNIho9p68tRzQ2MF1mFJ/qnd3ayGiasJtT7W7rbUQYNAtzL3n4CpmxVZi4msMamM+K7c0O7e6/pmQJ3HCmtpjurPs8MJzAJQf5dyTQQuDa44T9aOSi4ucDAnS+aETU20UH0C8HWU15un+T0FeIP8//J8VxHFkP4g6BzrHF9mA52QKgUnUTkxYcH1qVhL7TcKjvfGScG3MjnSV92hvpVlEDZhMNAGkCwORAPMG22r4moEOmLse0r4/tMkSZ/4InV5kc3NYmZFzigT3yBnZGmsTxyvVb1PdAGi0uNhIvS2Ay8nq6tARMz1lFMv3iz8y9MmwSwIsyMGIUq5xWG49/Fj9rqB6HvEj7lmojVhuGm4eBEiJ6dnuyu91l1O5sML68xbiaHk1lv+Q4Qy6THYKX5MKFcCTbFxfs+4acZRgquRLpLFDGkXtpJMs4XMjSCygcdKQy2bk6TSnncyhPFUiMCDkhavR/QICmtQw/RBJatqdf9+f6LT87zhA4g2jO9XXq6EcoaSTn0k7aS3eghXGRHumpYSNCDMXxuPnjI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 3.0.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added — Story RPG "Abenteuer" Mode

  • Story Engine: Full campaign-based RPG learning mode with branching narratives, skill checks, and character classes (Architect, Security, SysAdmin, Helpdesk). 20 campaigns across CompTIA Network+, Security+, CySA+, Linux+, and A+.
  • KI-Erzähler: Gemini-powered dynamic narrator with freetext player actions, NPC dialog generation, and role-based prompts. Fallback to static text when AI unavailable.
  • Skill Checks: In-story quiz phases with 3 questions per check, batch submission, pass/fail branching to different scenes.
  • 20 Campaigns: Real-world IT incident scenarios — SolarWinds, WannaCry, Log4Shell, Colonial Pipeline, Equifax, plus 6 Security+ exam-oriented campaigns (Phishing, Zero Trust, Crypto, Compliance, IR, Cloud) and an AI Security campaign.
  • Coop Mode: Infrastructure for multiplayer campaign sessions (UI placeholder, backend ready).

Added — "Zeitreise" Hack Through Time

  • 7 Epoch Campaign System: Phone Phreaking (1960s), WarGames (1980s), The Worm (1990s), Bobby Tables (2000s), Shadow Brokers (2010s), Supply Chain (2020s), Quantum Dawn (Future).
  • Period-Accurate CSS Themes: Each epoch has its own visual theme (terminal-green, amber-on-black, retro-web, etc.).
  • CHRONOS Guide: AI narrator character that guides players through hacking history.
  • Museum Facts: Historical sidebars with real dates, events, and technical details.
  • Character Classes: Epoch-specific roles (Phreaker, Hacker, Script Kiddie, Analyst, etc.).

Added — Visual Identity System

  • Design Tokens: CSS custom property layer (--lnc-*) with dark/light scopes and motion utilities.
  • 13 Character Registry: SVG silhouette avatars with emotion states (idle, celebrate, alert) for all NPCs.
  • Campaign Intro Animation: Full-screen intro with title, difficulty badge, and character reveal.
  • Dialogue Stage: NPC dialog component with portrait, speech bubble, and emotion tags.
  • Paper & Circuits Narrative Skin: Dark theme for adventure mode with subtle circuit-board patterns.

Added — RAG & Document Intelligence (v4.1)

  • Document Upload & Extraction: Instructors can link Nextcloud folders as course material, scan for files, and extract text from PDF/Markdown.
  • Chunking Pipeline: Automatic text chunking with keyword extraction for semantic search.
  • Multi-Source RAG Context: VirtuProf answers now cite course materials with [Quelle: filename, Kap. X] format.
  • 4000-Token Budget: Smart context window management prioritizing relevant chunks.

Added — Oldschool Board Games (v5.0)

  • Lernwürfel: "Mensch ärgere dich nicht" inspired board game with dice rolls and question challenges.
  • Wissensturm: Trivial Pursuit tower game with category-based progression.

Added — Personal Learning Bot (v4.0)

  • GeminiService: 5-layer security stack (API key isolation, input sanitization, output filtering, rate limiting, audit logging).
  • VirtuProf Chat: AI-powered learning assistant with chat-first UI, language auto-detection, and chat memory.
  • Note Generator: Gemini-based topic summaries saved to Nextcloud Files.
  • Lernprofil: Passive user learning profile with weekly study plan generation.
  • Auto-Triggers: Exam auto-notes, wrong-answer threshold alerts, weekly plan background job.
  • Ticket Triage: AI-powered support ticket classification and draft FAQ responses.
  • AI Consent: Opt-in consent overlay before first AI interaction, GDPR-compliant.

Added — Tools

  • Subnet Calculator: Browser-based IPv4 subnet calculator with CIDR/mask input, binary display, and VLSM planner.
  • Subnetting Question Pool: 50+ subnet calculation practice questions.

Added — Quality & Testing

  • ESLint: Vue/JS linting with no-v-html error rule, 0 errors enforced.
  • Pre-Push Hook: 4-gate quality check (Security scan, ESLint, Vitest 67 tests, PHPStan Level 5).
  • Playwright Browser Tests: 67 automated checks covering navigation, campaigns, time travel, tools, VirtuProf, security. 52 PASS / 11 FAIL / 4 SKIP.
  • PHPUnit: 12 service-level unit tests (Training, Leitner, Analytics, Course).
  • Test Strategy: 4-gate pyramid documented in CLAUDE.md.

Fixed

  • Campaign skill-checks now always use pre-loaded questions from scene response (no separate API call).
  • Campaign start always creates fresh session (no stale resume from previous play).
  • Missing Abenteuer/Oldschool/Zeitreise toggles in course mode settings.
  • WannaCry campaign: corrected from email dropper to SMB worm narrative.
  • PBQ subtype auto-detection from config structure when pbq_subtype not set.
  • Empty skill-check pools handled gracefully (skip to next scene instead of crash).
  • Narrative garbage fallback when Gemini returns malformed text.
  • AI rate-limits on VirtuProf available()/status() endpoints.
  • ESLint: empty catch blocks, v-if with v-for separation, escaped quotes in templates.

Security

  • Gemini language mirroring prevention (no prompt echo).
  • Story mode injection guards on freetext actions.
  • Story mode rate-limits (10 starts/min, 60 scenes/min).
  • Pre-push security scan for hardcoded secrets.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureMWUO0JmVVcGH0NNuBs36k/SJ6luN5fkUQPaxu+CxY+5FdW/X4uAAD6NgGuM/jQsqSyKsns4V1UgbtV15xhOlHyO8/fOrnIeU1tifv52U+Sud8OdnMeCy9eQ8TmbbZwrsxT7er+1hmRgrJPC+b1++4bWDE57D6tJ85xAeJ4TeNMkSQDAy8PGI7PnhG1ae0Xx4DAXAjIm6+GZqeletoRknfiLPpLyUcF2EMOqOxdWjXhDVEGhXMLgPUcJnLPi6avTjN/CCEK1awEzVr4IhaUUI86Dub/68KQmeOFZRusIARkqmY5Bq0ER9xzcWKf26pyl10xr1GJUh2sQB+Utw3cszgq6VPS8KdsVJ3a15+uqdinFxo8QHhJz409tF9RdQnqye+0WJ2qa4d4PbndPeAiAcu3z27tlmVDmwouvxrYco4wDgUSIW3lzayLjR8IiONHylfIZ56idwu/EAYEU2u9/0oKVDeGi+Izt6z4/aX9iTjuV29T1uB1e2aasUIWyRttCFBZ01PIaJqMfubHMvRmDrcjb9U4OKUTqLg58u29eeQMimHB9O3ij0n6zQn2MJdxnnahO5seMg5tuLwRETHbcxQ0yNLskSLvSDgIa8vBr/leUgUtBPUCbIx4msuV3Tuy7HpA9Lg+3C7ZkqOIne6k0vakIc4KPjAPL/SLBwZusxfDI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.6.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added

  • Calendar Token: Per-user ICS subscription token for external calendar apps. GET /api/v1/user/calendar-token returns token + subscription URL. POST /api/v1/user/calendar-token/regenerate invalidates old token. Token stored in NC preferences (never expires until regenerated).
  • Public ICS Feed: GET /api/v1/calendar/{token}.ics — unauthenticated ICS endpoint for calendar subscriptions. Uses token-to-user reverse lookup via oc_preferences.
  • Calendar Sync in Personal Settings: ICS URL field with one-click copy and regenerate button.
  • ICS UID domain fix: UID fields now use actual NC hostname (parse_url(getBaseUrl(), PHP_URL_HOST)) instead of literal @nextcloud.
  • AI Explain button: POST /api/ai/explain schedules a text2text task explaining why an answer is correct/incorrect. "💡 Explain this" button appears after answering in LeitnerMode and TrainingMode (only shown when AI is available).
  • At-Risk CSV Export: GET /api/courses/{courseId}/at-risk/export/csv — downloads at-risk student list as CSV (Name, Risk Level, Risk Reasons, Accuracy, Last Active). Export button in course progress tab.
  • Batch Pool Assignment: Add-pool modal in CourseDetail now supports multi-select (checkboxes) — select multiple pools and add them all with one click.

Changed

  • ExportController: Replaced anonymous class with DataDownloadResponse for ICS response (cleaner code, no custom render() override needed). ICS body extracted to buildIcsBody() — shared by authenticated and token-based endpoints.
  • AIService::getTaskStatus() now includes output (raw text) in status response alongside questions array — needed for explain results.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturegpWcIhT4IeeSqVyKoOjXGch+flHYM9TICkDbyl7oRxuCOBH6sfYOwu5upyqwzGKl05TIRX2bqIrJiNj+ULif8rORhf3hvt03mhVuDCU1RuH9AbsD9VNvyw5Rf69FDXWD/44jH4JBjQHhD0E9MoY/Kud6Cog6W7SmIXEmGlHNz7mbopa2OoZZws6AA+SwjkDvK9BbvDrae3brEkSc2udTdi2HqSa8tbKPouwE3vdEdHWJWF3/j7P+4GM5cDXIT0B4g9eyL8r8Q1AKO0Xu/1pGPddNmeDz4gK4cQFGJF1KhhgAMEA7EM9/7wkMW6hrONfD57PJywCdeJhMTGVgGbjzLRreJ57GuI4jPhjqumOsiXRS3Evyn5LmO/q7FXn54hl1VBZBb7Kdj/t46wZzeV+vkKP/TFu2a/Amr99KFDWnNhKaF0eg/xYBAyGdNE162L7lHjXUlu5o6d1iO8i8y1NQoexyta7cqo019m4/BjhAr1QWAo5QsFJQ6hD7t8oA6Es50yFIi39Vzn2vFrkNuoUkrGtUNUH5mxEoDTCaHeLKBokStXztfaXsZzRBsi7Eem70Hqh65PQ4GAA7/BIKksrGR9XXRbbQG6ZBa1NN2iZaPDNF6t+zbq9EcfpITq7aLOthuU2OEkW4zm2Ds6wrxE+gFU+fxCePWR5H7g3OJuOcwjk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.5.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added

  • Daily missions: mission progress model plus claim endpoint (GET /api/v1/missions, POST /api/v1/missions/{missionKey}/claim).
  • Mission XP claims table: Migration Version002000 adds learning_user_mission_claims with one-claim-per-mission-per-day guarantee.
  • Streak freeze tokens: weekly reset token support in streak calculation (streak_freeze_tokens, last_freeze_reset_week in learning_user_stats).
  • Badge tiers: new bronze/silver/gold/platinum tiers for session count, mastery count, and streak milestones.
  • Analytics UI updates: Daily mission cards with claim action and freeze-token visibility.

Changed

  • Streak handling: streak computation can bridge one missed day when freeze token is available; token is consumed on activity-side calls.
  • App version: bumped to 2.5.0.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureBtgrr+hdTfM0f4h9yPsjjpUL18GMWeoMLCyfuSk+f0aj+R2D8WpUaWos8YBBAdFtoTYGKeykyLd+Ww/8ckGusCAhrB8BiV2IDSKhna3EreyedyjQ7tI9KXrif6dvkN/8FQB3JsWSXyLujapZYGX0lsvVxwf6rzmLIsOhC5gAGD+aCHwsZDxzi2bWVr3iyMaQq1rqd7HxGMUZq6E67lRr2bKCXTK46QEX1TnGw/jtv7gMg5Kq0pG39oRgOTtpjNc/0VJR/KUw4PujcVYMaOyLOlJlNjN0bZjgsgyijcACSi5L3gF8/M7yKvHFwZK/MKaWozRsTu1fJBtvrblhCBeJvoAGanAW+1gCE4SyNzClU/ToY/utQGJWUFNhARuMHjmqkAiFjfoev57XvvKIlC3mBF9U8oRIL9uQY+O6D1d1oynxvyVLvCmMOzw36xzHcEr7Hygu6PJUEOS79QkROIBiivsT91fAgRyFnStnbrM+0Az8Jiyis1mRU3lzOOSEIomZoapMevwMgngBQUwfUPDeQysmaz5DdpmDNW6oHbjV8WwIItNkK2sZm4VoDMud+PyGbLBIG/3HBjQVS1Hrhc4cjpoBgVLASsIVh6qIzQAsJnpagFsxxAm9azNMiNm1vgvcqRUnVStk0hi4B4pO3pr76/K/h3m7Y7MJDu0n736r4Ww=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.2.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Fixed

  • Course Pool Access: Course members and instructors can now access pools assigned to their courses. Previously only pool owners and explicitly shared users had access, causing "Pool not found" errors for enrolled students.

Changed

  • PoolService: Extracted findPoolRow() helper, added hasCoursePoolAccess() fallback in find() method
  • 5 Services updated: LeitnerService, PoolService, QuestionService, TrainingService, TranslationService — all hasPoolAccess() methods now check course membership as a third access path

Technical

  • 5 changed files, 165 insertions, 12 deletions
  • No database migration needed — uses existing learning_course_pools and learning_course_members tables
  • Course pool access grants read-only permission (permission: 'read')
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturerHTnxsAiUtmAEk1HAE6AJN6jKvIyjYYyh58gOmB0JoBHJQyT5pFGKLy2mbGZ5ILVw/MqY1uD4/04K0b42tuj41OEKmXtIa53QcI2sBK0W3EeyAui2QmwXWlDUbaWYyo2BMarLHNt8WYuGcXM1erNLm0ucjsCAhCaW4QWwPeYU2j03TmyIhoi4MdUXXls6E0xJvLx3X8d/+lAJ5+JoFGXPkiK0celrDKW2jfiJP8t/GHWlutfA7HMmM6rzk9E+BgYMJgDLMZb93TVAs8TrTQDxQTjhN7zbWAd4l/QP1bbczF/tS8/wTW04yPJFoTr5HnNEHvsaPyB0oLmvvffxGmiwtKmyfTgf9vKBYARQGaj+khYGm2mKHTk5b4u1neqHJ92iO8CMPkj9ieg6AcZc2YV19URlTZMKbzn4lSxTfb4K5Gm+SXH/1heBs88fRNLaf5olKjuDiDExfxKlzzj+wiigXP9UF+va8sXj8JOz2TFMLQoPbTmnU1FOHgbtNJBcnnqe4SXz1eqRNSXQ2OGZW8bAmpo0oe4MK1CljeIIpIc+E5wjFZblWjw0NzIjoZEAkHg5SuHknsMDbcUqQPvCBJavpHM0L535Zmqpteh7H9kUw3H1BcD5hJyuAlL7FNKEtMO5TSOh7vFlCCNORllnXhi/i1oUL56gWGTbRUgiZs+fhE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.1.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • Free Text Questions: New open question type — users type free-text answers instead of selecting from choices. Fuzzy matching (case-insensitive, Levenshtein distance ≤2 for short answers, substring matching for long answers)
  • CSV/JSON Export: Download question pools as CSV or JSON files. Roundtrip-compatible with import — export then re-import produces identical questions
  • ExportController.php: New controller with exportCsv() and exportJson() endpoints, DataDownloadResponse for browser download
  • XP Streak Multipliers: Tier-based XP bonuses replace old linear formula — 1.5x at 3-day streak, 2x at 7-day, 3x at 30-day streak
  • XP Multiplier Badge: Visible multiplier indicator (1.5x / 2x / 3x) in Pool List when streak bonus is active
  • Open Question Import: CSV format question,model_answer,open and JSON "type": "open" with single model answer
  • Open Question in all modes: Training, Leitner, Smart Queue, Exam (textarea + batch submit), Daily Challenge — SwipeMode filters out open questions

Changed

  • XpService: getStreakMultiplier() and applyMultiplier() replace linear 1.0 + (streak * 0.01) formula
  • LeitnerService: All XP awards (correct answer, mastery bonus, daily goal) now use streak multiplier via XpService
  • UserStateController: state() response includes xp_multiplier field; answerChallenge() applies streak multiplier to challenge XP
  • QuestionForm.vue: New "Free text" answer type option with model answer textarea
  • QuestionList.vue: "Freitext" badge for open questions, model answer display, Export CSV/JSON dropdown
  • ImportDialog.vue: Help text updated with open-question format examples for both CSV and JSON
  • SwipeMode.vue: Filters out open questions (not compatible with swipe/tap interaction)
  • routes.php: 2 new export routes (71 total)

Technical

  • 1 new file (ExportController.php), 22 changed files
  • No database migration needed — question_type VARCHAR(20) already supports open, answer_ids TEXT stores JSON
  • QuestionService::isOpenAnswerCorrect() static helper used by TrainingService, LeitnerService, and UserStateController
  • Rate limit on export endpoints: 10 requests per 60 seconds
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureHyyUtHF7C32M+YZc15dn2kRI/DltaewXzt5Im3Zu3rPS12Q+ICmOK+AhT/TicLBFR3Zr21BjtyhcjcrFf5JmXIG9c65OEEyK2ApbXAa5HfFFhUbPIAjj+z2eG3F2+71DrFluPVg5imtKCJ1+zmZfNC3EFI+UFhUFPGor76KTJphQrT01qMaD21XuWlbr59lOI3PLuV+qENWBC2pvQMcBDTyfDEpanPR/NRZ9I31TqA5qj9d6su9UYxW0AO/21Dz6cTvUx6AeJksLyRjQsGEMZhvp5Lv3k3X4fiBp4q6j/VKtt/R89eCbNAjEcubZwYsAx6TA6gK1FtfOwO++7pU4EPzDlKFTBUpqoXJFvelF+SRg5h5oNJYU4zoJRCRV7IOIwliR//2G4Rh5bKenEELEhJ47l8lHfdw2vAkY1zMcOFRoTmda27VPD4W+uOBnd+7sXUSlPLCPRZYmZTMuFehgnPvLM8jN7e57ldBnzYw2AfSkS8vFmsIiT/TqhUknc7kFUAMD3Cz0PmofWWM+CbglCnD/HURBJW6FYX8dnA7fJhkyi7wSeC9nFc9TAU3jlVRiL/RA/wAmlxP2ZXJUk4JpuzxsdAwuBXkDRgnrmjnz31WdiqAI9f8HOPh9vc9ttbbMXP35llqMTa3T9NfnMZR0pkY0SKXcmSbAK3IHsv4dPI0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.0.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • Role-Based Navigation: Students start at Courses view (no Pools tab), instructors keep both tabs. App title simplified to "Lernen" for students
  • Onboarding Hint System: 7 dismissible info cards (localStorage-based) explaining Smart Queue, Leitner boxes, Daily Goal, Daily Challenge, and welcome messages for both roles
  • hintMixin.js: Reusable Vue mixin for localStorage-backed hint dismiss/check across all components
  • Mode Descriptions: One-line explanation shown below the active mode selector (Training, Leitner, Wahr/Falsch, Exam, Stats, Manage)
  • Pool-from-Course Navigation: "Back to Course" button when opening a pool from course context (instead of losing context by switching to Pools view)

Changed

  • App.vue: Mode selector filtered by role — students see only Train/Leitner/Wahr-Falsch/Exam (no Stats, no Manage)
  • PoolList.vue: Create Pool button, Share/Edit/Delete actions hidden for students. Better empty state text
  • CourseList.vue: Student empty state now explains that the instructor enrolls them
  • LeitnerMode.vue: Box labels improved — "Neu — taeglich wiederholen", "Lernphase — nach 1 Tag", "Bekannt — nach 3 Tagen", "Gut — nach 7 Tagen", "Gemeistert — nach 14 Tagen"
  • SmartQueue.vue: Better empty state ("Open a pool and start the Leitner system"), ready state shows priority sorting info
  • PoolList.vue: "Jetzt Lernen" → "Start learning", "Trouble Spots" → "Trouble spots" (consistency)
  • de.json: ~60 new German translation keys for all hints, mode descriptions, empty states, and v1.8/v1.9 UI strings
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureX22wHYsHapYtxGW+/4w3wuihpu0uy3Mi5x076ajJx5VbizQVWSF8uURw2bu+y/pQn4GZxinHK/JxHWqcCdU86MB3ThayzDX7ySTGx/z016eudjyAYAYGPx84KJ+ciGXFFiiS+QA7ebnPjnm/8O365ve4tVtv+7EqjHzOwntJnlT+GL9FAYiUS1z5UzFSuI02nNz3GXc3PG1NeWf3M1iAG9Q26zjv3n4q0mV1/GstjWvuJ5bAH/C8eNWtumTPjDq0/sBza15+DPzBtTuSDanieXSPi+EG2geKozRNe7fWBCWflVScnK38+9ke0vQsC+cgNMgsS+t9Qquv1IHQqbQLzEpFRq88LcWfar9VH0YFG9E1FaX2XrbF+59lZdM39NvwLrOCIfJFt7Qpnv/9Zkv4voezhaST8lKLI7FC/jCNhDcN6IpLPj3a8VuYsyMfu2vq3A1VDAyq7qb7T0l7T/FTRVQG2DlxZLnRMSMsQgFHmbiP2/5S553/N9JErAgrY85op8ucOCv83ysvUOYLVNtXHbPq9YyH+7GXDK0OsIvIfoK+gsaj/qcBt2x88FH1SlpY0VLDkZ7huBC21rvKTV0lT3sXyNo0H2HKPGR14Xm8DsN5GCIaljf0ErOJRfsxX9nUpDqU5aqWk8ABK+kyTldmwViOizIzc0uMMdeu0G7bUWE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.9.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • AI Question Generator: Paste text (lecture notes, textbook excerpts) and let AI generate multiple-choice questions. Three-step workflow: input → AI generation → editable preview → bulk import into pool
  • AIService.php: NC TaskProcessing API (NC 30+) with TextProcessing fallback (NC 29) — runtime detection, no app-level API key management
  • AIController.php: 4 new endpoints — GET /api/ai/available, POST /api/ai/generate, GET /api/ai/status/{taskId}, POST /api/ai/import/{taskId}
  • AIGenerator.vue: Full wizard with textarea input, question count slider (5-30), language selector, polling progress, editable preview with select/deselect all, inline editing of questions and answers
  • At-Risk Early Warning: Instructors see which students are falling behind on the Course Progress tab. Rule-based risk scoring with 5 weighted signals (inactivity >7d, accuracy <50%, box-1 stall >60%, lost streak, <3 sessions in 14d)
  • At-Risk API: GET /api/courses/{courseId}/at-risk — returns students with risk_level (high/medium), risk_reasons array, last_active, accuracy
  • Remediation Queue: Focused practice on hardest questions (≥3 wrong answers, <30% accuracy, box ≤2). "Trouble Spots" button on Pool List with item count
  • Remediation API: GET /api/leitner/remediation (items), GET /api/leitner/remediation/count (lightweight count)
  • Daily Challenge: One random question per day from user's pools with +15 XP bonus for correct answers. Deterministic selection via crc32(userId + date) — same question per user per day
  • Daily Challenge API: GET /api/v1/daily-challenge, POST /api/v1/daily-challenge/answer
  • Migration V001600: Adds last_challenge_date (VARCHAR 10) and last_challenge_correct (BOOLEAN) to learning_user_stats

Changed

  • QuestionList.vue: "Generate with AI" button visible when AI provider is configured (checks /api/ai/available)
  • PoolList.vue: "Trouble Spots" button (orange, warning style) and inline Daily Challenge card with answer submission
  • SmartQueue.vue: New mode prop (queue | remediation) with conditional API endpoint, titles, and empty states
  • App.vue: Wires openRemediation event from PoolList to SmartQueue with mode="remediation", new smartQueueMode state
  • CourseDetail.vue: At-Risk section on Progress tab — collapsible card grid with risk badges and reason tags
  • routes.php: 9 new routes (ai: 4, leitner: 2, user_state: 2, course: 1) — 69 total

Security

  • AI Rate Limits: generate endpoint limited to 5/min, import limited to 10/min
  • AI Input Validation: Text length 50-50000 chars, word count capped at 3000 for LLM input
  • AI User Isolation: Task status checks verify userId matches task owner
  • At-Risk Access Control: Endpoint restricted to course instructors via validateInstructor()
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
Signaturedp2AlnFQwp1Cy8OCA/kE3gVqTvYhN9ORN/Oa86Lk0jjsYVLVFDHML+2ga4rlw5rjLTzojVtzB4BM1uJdyu/fRrdCUz9DhBBxIgNLNto8oIzu6HOYkE4TCB38quz5MGBbCVvZWnjmKb0ugYvzixvpy7ZPJnAWB5bwfj8C8AJqeEbbqqClkbjE4SAO9sxLfJbzDyWK7au4/It+H5gOO1yzXzUL6azX31M4acCwXZNI8AMRj6UHHqudtLsb9WgEC+cIHGEOfArbdIN9MDh1eDU3R17uuPjJw19g0KqZsc/NqGb/XuPUmHOHdcr8XzHDO4gYxp6FTEiMR8anCFogfEzpfrwGr/RAFzFBI0zfZ0+cbLHCNW3xrPVy/hOKGTlYXUHroalE6cvYdniSoXvQ09m6GlynsCauLZ8+hCb679ewlremqakW3E5RLtPL+xkP/ti0b8nev0Mdd9smil0YbrtPA6kJyk5JgllkPT3xhDDOtYrH+yrX2VNp5f4ypdWkPS8hM/SHId2W5q2D6K9XHVymde1aeA7AYPfa5/c62k7bvmwLbbNOWF4stKfq8MCA83RXASbfeHnAbdNbFltSPB9CT8oHBlBikwxk32iVKyoGEzuKZbAtZhC1x69ryyO0Uw5BAx6vexGDJUdgQb3PyeN5i2tV3Z2s3GCtHMjF+y0E8tY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.6.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • ConsistencyCheckJob: Daily background job that reconciles learning_user_stats against source-of-truth tables — self-healing for XP, level, sessions, mastered counts, and streaks
  • Composite Leitner indices: (user_id, box) and (user_id, next_review) on learning_leitner_items for faster aggregations and due-question queries (Migration Version001300)

Fixed

  • HIGH: LeitnerService::answerQuestion() now wraps box update, stats update, XP increment, and badge award in a single DB transaction — prevents partial writes on crashes
  • HIGH: Optimistic concurrency control on Leitner item update (WHERE box = :oldBox AND correct_count = :old AND incorrect_count = :old) — detects and rejects concurrent modifications including Box-5→5 lost updates
  • HIGH: Box-5 mastery bonus (+25 XP, +1 mastered, badge check) now only awarded on actual promotion (Box <5→5), not on repeated correct answers in Box 5 — prevents XP/stats inflation
  • HIGH: Badge notifications and Activity events now dispatched after transaction commit — prevents ghost notifications on rollback
  • MEDIUM: Demotion fallback (updateUserStats()) moved after Leitner item update — recalc now sees correct box value instead of overcounting
  • MEDIUM: syncLevel() deferred to after transaction commit — eliminates lock contention under concurrent reviews
  • MEDIUM: Cache invalidation moved after transaction commit (was inside write sequence, could serve stale data on rollback)
  • LOW: Multi-select answer validation uses batch IN query instead of N+1 per-answer queries

Performance

  • Leitner box aggregations (stats, mastery counts) use composite index instead of full table scan
  • Due-question queries benefit from (user_id, next_review) index — faster Leitner review loading
  • ConsistencyCheckJob processes max 500 users per run, oldest-updated first — all users eventually reconciled
  • ConsistencyCheckJob logs progress and catches per-user errors without aborting the batch
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureKQnhIvGaYFr9qdGYI0R0k4GLBar4u3swxmHkT+MfcE1phTVpyAm8O9s/s1TIz7qIDG3TA7CcjVDPTh57M2WysEi8/Yu8OZgE2pjJbsTOOssjRtFoGUqzyU4AR74azP5w3f/wdgk0jiIEQvOV+4aM3fENSOSktpfeGZzhch0xHUfON9kS2h4x8+8AqH9M/xkPJvS0dUIUoGNGYsug8qu3HkRcBxsH7n4hYzg3ycx+eL8vWpwfhFOhAV70cpcHOrK/oVz0ra4tl06LdPDNWeCB3AYCHU8hcehaPr+VsNJmgOdBoRf7JbesAu5YOB25qy340wJ+XlLonXe03NTq+xoG67Na0laU9cs+j56X856ik8G0UeT4vIywTObfh0iKmtqk/pUKzv0YopTYl/iSPtU/hTquoXnSYbEALoyPyh9aJpgpKXEn7d7qyoMo5/9Bx1owdXNxIi7gGtue1MMrFTkCIEm3/SMFWmA4XBXSRt2zP4sEUKaE1L8STzO57jFtpzt/WfC47ccCRktkLEkXDIIpwYwjHQYol7P/luO1OqUwJVefFnC52Fc4tJDrabMIbLqfOqwUPip0o18zcqsaNpTVyc6Mw+VLgO6MkP6lmdWfq0nvpBpKMu5e7cySJoT440xsvCBTWIIcfQyYcMJGxFscSondANcYYAg/ixhtChuTd5U=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.5.0
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Added

  • Consolidated User State API: GET /api/v1/user/state — single endpoint replaces 4 separate calls (XP, streak, badges, progress)
  • Denormalized learning_user_stats table: O(1) reads for XP, level, streak, sessions, mastered counts (Migration Version001200)
  • Background Notification Job: NotificationJob (TimedJob every 4h) replaces widget-triggered notifications
  • Activity-App Integration: Badge unlock events appear in Nextcloud Activity stream with toggle in Activity settings
  • Timezone-aware badges: Night Owl and Early Bird badges respect user's Nextcloud timezone setting
  • XpService: Dedicated service for XP calculation, session XP increment, level computation
  • Response caching: User state endpoint cached for 30s per user via ICacheFactory
  • Composite index on learning_sessions(user_id, completed_at) for faster XP/streak queries
  • time_limit_seconds column on learning_sessions for Speed Demon badge support

Changed

  • BadgeService refactored from 430 to ~200 lines — XP/streak methods extracted to XpService
  • Dashboard Widget is now pure read-only (~140 lines) — no longer sends notifications as side-effect
  • AnalyticsDashboard.vue: 4 API calls consolidated to 1, with fallback to legacy endpoints for rolling deploys
  • countUp.js: Respects prefers-reduced-motion — immediately shows final value without animation
  • BadgeUnlock.vue: Added aria-live="polite" region for screenreader announcements
  • LeitnerService: Box-5 promotion updates denormalized total_mastered counter
  • TrainingService: Session completion updates denormalized stats via XpService

Fixed

  • HIGH: Leitner XP now synced to learning_user_stats — correct answers award 5 XP, Box-5 mastery awards 25 XP bonus
  • HIGH: Race condition in stats UPSERT — all INSERT paths wrapped in UniqueConstraintViolationException catch with UPDATE retry
  • HIGH: SQL injection in incrementSessionXp$currentStreak now uses createNamedParameter() instead of string concatenation
  • HIGH: NotificationJob memory exhaustion — fetchAll() replaced with streaming fetch() loop; N+1 due-count queries replaced with single batch query
  • HIGH: Level-race in XpService — stale getTotalXpFromStats() read eliminated; syncLevel() is monotonic (AND current_level < :newLevel) to prevent concurrent stale-level overwrites
  • HIGH: Stats INSERT fallback uses updateUserStats() full recalc — preserves historical XP, sessions, and mastered counts for first-write users
  • HIGH: total_mastered demotion — Box 5→1 transition now decrements total_mastered counter with updateUserStats() fallback for missing stats rows
  • MEDIUM: User state cache invalidated on every Leitner answer (not just correct), after share-badge awards, and at END of completeSession() (after all badge/streak writes)
  • MEDIUM: Migration backfill is per-step idempotent — each INSERT...SELECT skips existing users via NOT IN clause (safe for partial re-runs)
  • MEDIUM: Migration backfill includes Leitner-only users (no completed sessions but have reviewed cards)
  • MEDIUM: Frontend fallback triggers on any error (not just 404) for rolling-deploy resilience
  • LOW: Removed dead $totalMastered variable in LeitnerService (computed but never used)

Performance

  • Dashboard load: 4 XHR → 1 XHR for gamification data
  • XP calculation: O(n) SQL aggregate → O(1) stats table read
  • Notifications: synchronous widget side-effect → async background job
  • NotificationJob: filters to users active in last 30 days (skips dormant accounts on large instances)
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureOyK9vuzcblU3s5znaUXHm8WvkECMxcl4Jz5Xt1pNg4mTSvtO0R26bat9p9AMH75j70QPKj0FUJYI6jhObMU7X/lUu6b6+UhvO+RCtzO3/4OQ+gwULA4qSX4oWzmiG6obxmI51H0AnBOCwLb/JuVlbJJzDWuaSetL7fZX5oOEGoNuom7ZK3eoPovLG0d94fhldGhXBph8D6iUsySsmxuA1nU/nFE8kFTXAj89xHxPb1V6G4omNw+0K7mfjshDC++A44/zibOE/XvHYhLPmch/MvvHyPkJ7jMXzhOsSJYB+EAQMGEVMig4tOf6Cug9HFd3+adiHIPP+UpyI3R1lZP5YNSMsuvDviDHXI16P25mr87PZO6/XcOxbwRV+eG7aiRlRTtgvgbfCPQacIJfT+zzZzGytY2sa+htxiTis0sdQY2BdsdtlwD75NmlP37KEZFJuG6y+4TTV7kN6iTueYxfoYTcQ9irz82FrgNYTAgWAgNXJ6K9rnlHlBAc7ZU9+VT6tNzi39eJflwwLJKPjhPyBKiZ10ycIPwgJYLAtbTQdaYF7rmBDVM8xkS+KSjvyErD+vzV+lZRVy6G2zHZpiRhPSFHGc1ytWa3o180ukeo0fw2/fz29R/OcIxFupinMuAPeaoHE2yrGkHvg8yJSHq2Scen7Bp1AhNmiL8BIbSAHe4=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.4.0
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Added

  • Achievements / Badges: 14 badges across 6 categories (sessions, performance, mastery, streak, social, fun)
  • XP & Levels: Experience points calculated from sessions, Leitner reviews, accuracy bonuses, and streak multiplier
  • Badge Unlock Overlay: Animated full-screen overlay with confetti when earning a badge
  • Analytics Dashboard: Achievements grid with earned/locked status, badge progress bars, XP level bar
  • Session Juice: Animated score count-up (countUp.js), Personal Best detection, improvement vs. average display
  • XP display: "+X XP" shown on all results screens (Training, Exam, Swipe, Leitner)
  • Nextcloud Notifications: Badge unlock notifications, streak-at-risk warnings, due card reminders
  • Badge progress tracking: API endpoint showing progress towards unearned badges
  • New DB table learning_user_badges (Migration Version001100)
  • 2 new API endpoints: GET /api/badges, GET /api/badges/progress

Changed

  • Training complete response now includes newly_earned_badges, xp_earned, is_personal_best, improvement, average_accuracy
  • Leitner answer response now includes newly_earned_badges when card reaches Box 5
  • Share creation now triggers badge check for "Sharing is Caring"
  • Streak endpoint now triggers streak badge checks
  • Dashboard widget sends streak warning and due reminder notifications
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYuFGppj5TQhlB5nd+fXqJurxGQUn7l0FFIqbWhXJMf9xPEi/g3wKdx7qukdK2ttz1Rf5xEbSdoLsGj3q1F7V5DZ+4+4GmMewS+MlYE4GA9ESFIA6lHLYxThICgb3G2lEGtImmp57tmGv7dzAiPqIH4pEdC0KT/iO08+VGy5R5s1gS5r36nHSrytf5C7JUBSSmOg6rVpUFIccDaXv8zn1f9sWWAZw4pRptDx+X29foqY2Zt4S/Swuz/SOGWKyfhhPw/x6RHjB3vnJIxx64Oi4Rom8BNdrU911RtMUFXLcnxq4xsv7VO2wGMkjNHegpL2yNr68yJSEm0bOjmVih06XMpJDNC0KN4S0vwnUkDIm1t9Dk1zxSPK5ba/PlQrTFxMVwratZsc1Uz0b/VqUBaZTu9oEBMpBm4CTrSf8XK29CNQ5EQXAzMSB0UUz0TqPpL2rI9kkcejJl0cololPd8N5R5jJpQ5miguwm6UjfTrIJzaizm6MAtXg+wx/DFfTyaSS936v1JEaRGIy4XkeQ9ZAAnSANlx9o8HTyLnWc7v6EsdkSo/7q88h7FaFUrlgkb3Gv7CQLBIJZ2jLtYa/2jv5gHQrg3c9unmIPLK8njpwS5+KgMH1V8LsyvraUW9ejzfmGv/BuN+reNyvToEoP8JIFPLs7SP1areV9svmkEnjIT8=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.3.4
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Security

  • CRITICAL: Close training-session oracle — startSession(exam) auto-completes open training sessions on same pool
  • CRITICAL: Defense-in-depth in submitAnswer/submitBatch — suppress correct_answer fields via cross-session check
  • HIGH: Strip explanation field from Question API during active exam to prevent indirect answer leakage
  • Rate-limits on ShareController and TranslationController write endpoints

Fixed

  • Leitner feedback shows hint when correct answers are suppressed during active exam
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureajCD73Wim+3CcebKFs72Co993wGojtfsPy8b+Gde+kx+EhPmDpzYEGdgbb54ZBqpDmJAroHIbaqZa0FfK8SwvFwC2JZ7WOp4xwOAjp+V7ruKvXu24MmzsH2wUp9U2ec+sO+EG9+ebsDELjaFEf2m6EMbRhw+/QUzsE51kaA10RaTwoNfTu29LLraGaEEFVWh9R5x5jbqNj9Wdvfq/vNNTp/XTm4SBLnAsD+7ymyMucfwHyn4ZYGspXdNd8v0HS6ZRe3JjtcOd+IjwEebZ4b2KPBCrnGs8sr+6zer7FItbLUOq2MxxdhaOml1yTQIPzeyuJPnFvmLJOw0FHJvsn/ZMMjCpByVSrf2MBua3yW4DcB/Z9m3yBwM3lQ98QUQDgmAfHwU1zG3P5ijvxFvaOz5XW3sFsCaktXfGtFfrMgUgbN8aee/7hZkA36Vi+zIUsbs+eExv5QvDn6bG21DrE8022vnfZ7gY9HFQESVMGsnZc9yqdeGJn9F09jScGcZvyinjtUUCe2/SRfjPHHa9JWvZ84pQuO+0OyAbvIEOlwz64r5qNatt47yoxAEzEUsu8oFQP2s+PX/Ni/yyEIWbH4LaK0qCPTulJKvujaq1fukSchT+9UMBVKxEebC672M1LtejtJnJfZVUp6WuAecz2QmBU4Va6WFDVLSkbAiz6ON/Jg=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0

Nextcloud 29

Learning 3.6.0
Release Details
UpdatedJune 24, 2026, 9:22 a.m.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturejQ1Xdi636ODYsfoC8Sr3MU9s6K0a/PIOZvbo0ea4D/TpW/NiVKu49cZC8JLt8oFbyp2qVI27LVBXEplSF6V004H1NMqHdnC3zhqEbs/BzRH9CIKZW7Ouzs2Qj7obBiBiszYtamVpxRvxt3bfCiIfac9mn6yMJ2OvsqDohraIT5Mh84kyuW4ibPVun/8hX2/qndkvsQQvKnO+GL8UHRV1UhiSG/ezTdjZbinc0ZVU06oWXJFuP2YXpFPc+GHG8RMBP6N3KeqmK+rMZ6lKaZY5fl//pnr8AbASDKYK0MjWQFsqLrei9R/iQbsHqjlT3vT5o0Sssrw0m++5ysCJhTb0uEPgeLvg4DUOige/iCegy+NZFb+qgb7oezetZD8s/aWxZTR645y1MZS4va5kgdAVreT0rvHDDZvx9UuKr4smMvAxX1DEUKgzqP/AMellHcwb4BVE9pVwOENp5oKttpoJXKoH/Lnwu+o+1o5m4+JH/SHHGJBCmXYeehFscARZceC9lNYsBTHL+uKILOszxJXK0OkwrFCVCGTnXFfO0hn23gwjECPGP/zwB8OwlwedZXjaZkAMomIulAxghFIw5arSOoX85kv7bOlwerVJ7jfHs8GYIqBn3b+bWzVn5MQEumuUiKBi01WH7vbjn5Neuxi7YfSBkjJjeQFOFlGEmwFwcrs=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 3.4.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Changed — UX-Navigation Struktur

  • Instructor Tab Groups: 17 instructor tabs organized into 5 logical groups (Lernraum, Teilnehmer, Kommunikation, Wettbewerb, Verwaltung) with visual separators.
  • Abenteuer Standalone: Adventure mode promoted from Arena sub-mode to its own CourseDetail tab, independently gatable via mode_config.
  • Arena Submode Gating: Each Arena mode (Duel, Gameshow, Oldschool) individually hideable per course rules. Arena tab disappears when all sub-modes disabled.

Changed — Code-Hygiene & Settings

  • Settings Split: Instructors now see two sub-tabs in Settings: "Kurs-Verwaltung" (admin) and "Meine Einstellungen" (personal). Students see only personal settings.
  • Zeitreise Removed: Removed 1270+ lines of dead Zeitreise/HackThroughTime frontend code (component, characters, navigation, mode_config key).
  • German Labels: All UI-visible labels now go through t() translation. ModeIdentityBanner labels localized.

Added — Simulator-Praxis-Sessions

  • Practicum Engine: Pure-JS state machine for guided session management with localStorage persistence and browser-reload recovery.
  • 11 Practicum Sessions: Real-world IT scenarios across all 7 simulators (42 steps total). Firewall, DNS, and Routing get 2 sessions each.
  • PracticumRunner UI: New "Praxis" tab in every simulator with step-by-step instructions, context explanations, progress bar ("Schritt X von Y"), and score summary.

Added — Student Dashboard

  • Heute Screen: New default landing page for students with SmartQueue widget (due cards count + "Jetzt lernen"), Daily Challenge card, streak display, and daily progress.
  • Global Feed: Aggregates announcements from all enrolled courses chronologically with course-name badges and pagination.
  • Pools Navigation: Direct "Pools" tab in student main navigation — one click to PoolList.

Added — DevCloud Integration & Leitner

  • Talk Room Link: Instructors can set a Talk room token per course. Clickable link appears in course header, opens NC Talk in new tab.
  • Materials for Students: Students now see the Materialien tab (read-only) when a course has a material folder set.
  • Buddy Matching: New "Lernpartner" tab shows who can help with your topics and whom you can help, based on Telos help_offer/help_wanted data.
  • Course-Aware Tools: Werkzeuge tab filters simulators based on active course's enabled_tools. No course active = all tools visible.
  • Sprint Intervals: Instructors can activate sprint mode per course (4h/12h/1d/2d instead of 1d/3d/7d/14d Leitner intervals) for intensive courses.

Fixed

  • Badge Duplicate Crash: Session completion no longer fails with 400 when badge already exists. Fixed catch to use OCP\DB\Exception (NC 30 wraps Doctrine exceptions).
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
Signaturely5UiDEZdUJh6uSRREEijl3s87EIDnAeze3Y9KBfIECnaFvdafIwluTTl1XeyEvAaGshOHIXMmtfVvnYA9dMLyIj+jKvSeWlFKgbTsMS7hRvCiuqGcoFBPuOm8rDzIk6AXTGieQFgNIho9p68tRzQ2MF1mFJ/qnd3ayGiasJtT7W7rbUQYNAtzL3n4CpmxVZi4msMamM+K7c0O7e6/pmQJ3HCmtpjurPs8MJzAJQf5dyTQQuDa44T9aOSi4ucDAnS+aETU20UH0C8HWU15un+T0FeIP8//J8VxHFkP4g6BzrHF9mA52QKgUnUTkxYcH1qVhL7TcKjvfGScG3MjnSV92hvpVlEDZhMNAGkCwORAPMG22r4moEOmLse0r4/tMkSZ/4InV5kc3NYmZFzigT3yBnZGmsTxyvVb1PdAGi0uNhIvS2Ay8nq6tARMz1lFMv3iz8y9MmwSwIsyMGIUq5xWG49/Fj9rqB6HvEj7lmojVhuGm4eBEiJ6dnuyu91l1O5sML68xbiaHk1lv+Q4Qy6THYKX5MKFcCTbFxfs+4acZRgquRLpLFDGkXtpJMs4XMjSCygcdKQy2bk6TSnncyhPFUiMCDkhavR/QICmtQw/RBJatqdf9+f6LT87zhA4g2jO9XXq6EcoaSTn0k7aS3eghXGRHumpYSNCDMXxuPnjI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 3.0.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added — Story RPG "Abenteuer" Mode

  • Story Engine: Full campaign-based RPG learning mode with branching narratives, skill checks, and character classes (Architect, Security, SysAdmin, Helpdesk). 20 campaigns across CompTIA Network+, Security+, CySA+, Linux+, and A+.
  • KI-Erzähler: Gemini-powered dynamic narrator with freetext player actions, NPC dialog generation, and role-based prompts. Fallback to static text when AI unavailable.
  • Skill Checks: In-story quiz phases with 3 questions per check, batch submission, pass/fail branching to different scenes.
  • 20 Campaigns: Real-world IT incident scenarios — SolarWinds, WannaCry, Log4Shell, Colonial Pipeline, Equifax, plus 6 Security+ exam-oriented campaigns (Phishing, Zero Trust, Crypto, Compliance, IR, Cloud) and an AI Security campaign.
  • Coop Mode: Infrastructure for multiplayer campaign sessions (UI placeholder, backend ready).

Added — "Zeitreise" Hack Through Time

  • 7 Epoch Campaign System: Phone Phreaking (1960s), WarGames (1980s), The Worm (1990s), Bobby Tables (2000s), Shadow Brokers (2010s), Supply Chain (2020s), Quantum Dawn (Future).
  • Period-Accurate CSS Themes: Each epoch has its own visual theme (terminal-green, amber-on-black, retro-web, etc.).
  • CHRONOS Guide: AI narrator character that guides players through hacking history.
  • Museum Facts: Historical sidebars with real dates, events, and technical details.
  • Character Classes: Epoch-specific roles (Phreaker, Hacker, Script Kiddie, Analyst, etc.).

Added — Visual Identity System

  • Design Tokens: CSS custom property layer (--lnc-*) with dark/light scopes and motion utilities.
  • 13 Character Registry: SVG silhouette avatars with emotion states (idle, celebrate, alert) for all NPCs.
  • Campaign Intro Animation: Full-screen intro with title, difficulty badge, and character reveal.
  • Dialogue Stage: NPC dialog component with portrait, speech bubble, and emotion tags.
  • Paper & Circuits Narrative Skin: Dark theme for adventure mode with subtle circuit-board patterns.

Added — RAG & Document Intelligence (v4.1)

  • Document Upload & Extraction: Instructors can link Nextcloud folders as course material, scan for files, and extract text from PDF/Markdown.
  • Chunking Pipeline: Automatic text chunking with keyword extraction for semantic search.
  • Multi-Source RAG Context: VirtuProf answers now cite course materials with [Quelle: filename, Kap. X] format.
  • 4000-Token Budget: Smart context window management prioritizing relevant chunks.

Added — Oldschool Board Games (v5.0)

  • Lernwürfel: "Mensch ärgere dich nicht" inspired board game with dice rolls and question challenges.
  • Wissensturm: Trivial Pursuit tower game with category-based progression.

Added — Personal Learning Bot (v4.0)

  • GeminiService: 5-layer security stack (API key isolation, input sanitization, output filtering, rate limiting, audit logging).
  • VirtuProf Chat: AI-powered learning assistant with chat-first UI, language auto-detection, and chat memory.
  • Note Generator: Gemini-based topic summaries saved to Nextcloud Files.
  • Lernprofil: Passive user learning profile with weekly study plan generation.
  • Auto-Triggers: Exam auto-notes, wrong-answer threshold alerts, weekly plan background job.
  • Ticket Triage: AI-powered support ticket classification and draft FAQ responses.
  • AI Consent: Opt-in consent overlay before first AI interaction, GDPR-compliant.

Added — Tools

  • Subnet Calculator: Browser-based IPv4 subnet calculator with CIDR/mask input, binary display, and VLSM planner.
  • Subnetting Question Pool: 50+ subnet calculation practice questions.

Added — Quality & Testing

  • ESLint: Vue/JS linting with no-v-html error rule, 0 errors enforced.
  • Pre-Push Hook: 4-gate quality check (Security scan, ESLint, Vitest 67 tests, PHPStan Level 5).
  • Playwright Browser Tests: 67 automated checks covering navigation, campaigns, time travel, tools, VirtuProf, security. 52 PASS / 11 FAIL / 4 SKIP.
  • PHPUnit: 12 service-level unit tests (Training, Leitner, Analytics, Course).
  • Test Strategy: 4-gate pyramid documented in CLAUDE.md.

Fixed

  • Campaign skill-checks now always use pre-loaded questions from scene response (no separate API call).
  • Campaign start always creates fresh session (no stale resume from previous play).
  • Missing Abenteuer/Oldschool/Zeitreise toggles in course mode settings.
  • WannaCry campaign: corrected from email dropper to SMB worm narrative.
  • PBQ subtype auto-detection from config structure when pbq_subtype not set.
  • Empty skill-check pools handled gracefully (skip to next scene instead of crash).
  • Narrative garbage fallback when Gemini returns malformed text.
  • AI rate-limits on VirtuProf available()/status() endpoints.
  • ESLint: empty catch blocks, v-if with v-for separation, escaped quotes in templates.

Security

  • Gemini language mirroring prevention (no prompt echo).
  • Story mode injection guards on freetext actions.
  • Story mode rate-limits (10 starts/min, 60 scenes/min).
  • Pre-push security scan for hardcoded secrets.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureMWUO0JmVVcGH0NNuBs36k/SJ6luN5fkUQPaxu+CxY+5FdW/X4uAAD6NgGuM/jQsqSyKsns4V1UgbtV15xhOlHyO8/fOrnIeU1tifv52U+Sud8OdnMeCy9eQ8TmbbZwrsxT7er+1hmRgrJPC+b1++4bWDE57D6tJ85xAeJ4TeNMkSQDAy8PGI7PnhG1ae0Xx4DAXAjIm6+GZqeletoRknfiLPpLyUcF2EMOqOxdWjXhDVEGhXMLgPUcJnLPi6avTjN/CCEK1awEzVr4IhaUUI86Dub/68KQmeOFZRusIARkqmY5Bq0ER9xzcWKf26pyl10xr1GJUh2sQB+Utw3cszgq6VPS8KdsVJ3a15+uqdinFxo8QHhJz409tF9RdQnqye+0WJ2qa4d4PbndPeAiAcu3z27tlmVDmwouvxrYco4wDgUSIW3lzayLjR8IiONHylfIZ56idwu/EAYEU2u9/0oKVDeGi+Izt6z4/aX9iTjuV29T1uB1e2aasUIWyRttCFBZ01PIaJqMfubHMvRmDrcjb9U4OKUTqLg58u29eeQMimHB9O3ij0n6zQn2MJdxnnahO5seMg5tuLwRETHbcxQ0yNLskSLvSDgIa8vBr/leUgUtBPUCbIx4msuV3Tuy7HpA9Lg+3C7ZkqOIne6k0vakIc4KPjAPL/SLBwZusxfDI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.6.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added

  • Calendar Token: Per-user ICS subscription token for external calendar apps. GET /api/v1/user/calendar-token returns token + subscription URL. POST /api/v1/user/calendar-token/regenerate invalidates old token. Token stored in NC preferences (never expires until regenerated).
  • Public ICS Feed: GET /api/v1/calendar/{token}.ics — unauthenticated ICS endpoint for calendar subscriptions. Uses token-to-user reverse lookup via oc_preferences.
  • Calendar Sync in Personal Settings: ICS URL field with one-click copy and regenerate button.
  • ICS UID domain fix: UID fields now use actual NC hostname (parse_url(getBaseUrl(), PHP_URL_HOST)) instead of literal @nextcloud.
  • AI Explain button: POST /api/ai/explain schedules a text2text task explaining why an answer is correct/incorrect. "💡 Explain this" button appears after answering in LeitnerMode and TrainingMode (only shown when AI is available).
  • At-Risk CSV Export: GET /api/courses/{courseId}/at-risk/export/csv — downloads at-risk student list as CSV (Name, Risk Level, Risk Reasons, Accuracy, Last Active). Export button in course progress tab.
  • Batch Pool Assignment: Add-pool modal in CourseDetail now supports multi-select (checkboxes) — select multiple pools and add them all with one click.

Changed

  • ExportController: Replaced anonymous class with DataDownloadResponse for ICS response (cleaner code, no custom render() override needed). ICS body extracted to buildIcsBody() — shared by authenticated and token-based endpoints.
  • AIService::getTaskStatus() now includes output (raw text) in status response alongside questions array — needed for explain results.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturegpWcIhT4IeeSqVyKoOjXGch+flHYM9TICkDbyl7oRxuCOBH6sfYOwu5upyqwzGKl05TIRX2bqIrJiNj+ULif8rORhf3hvt03mhVuDCU1RuH9AbsD9VNvyw5Rf69FDXWD/44jH4JBjQHhD0E9MoY/Kud6Cog6W7SmIXEmGlHNz7mbopa2OoZZws6AA+SwjkDvK9BbvDrae3brEkSc2udTdi2HqSa8tbKPouwE3vdEdHWJWF3/j7P+4GM5cDXIT0B4g9eyL8r8Q1AKO0Xu/1pGPddNmeDz4gK4cQFGJF1KhhgAMEA7EM9/7wkMW6hrONfD57PJywCdeJhMTGVgGbjzLRreJ57GuI4jPhjqumOsiXRS3Evyn5LmO/q7FXn54hl1VBZBb7Kdj/t46wZzeV+vkKP/TFu2a/Amr99KFDWnNhKaF0eg/xYBAyGdNE162L7lHjXUlu5o6d1iO8i8y1NQoexyta7cqo019m4/BjhAr1QWAo5QsFJQ6hD7t8oA6Es50yFIi39Vzn2vFrkNuoUkrGtUNUH5mxEoDTCaHeLKBokStXztfaXsZzRBsi7Eem70Hqh65PQ4GAA7/BIKksrGR9XXRbbQG6ZBa1NN2iZaPDNF6t+zbq9EcfpITq7aLOthuU2OEkW4zm2Ds6wrxE+gFU+fxCePWR5H7g3OJuOcwjk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.5.0
Release Details
UpdatedJune 24, 2026, 9:21 a.m.
Changelog

Added

  • Daily missions: mission progress model plus claim endpoint (GET /api/v1/missions, POST /api/v1/missions/{missionKey}/claim).
  • Mission XP claims table: Migration Version002000 adds learning_user_mission_claims with one-claim-per-mission-per-day guarantee.
  • Streak freeze tokens: weekly reset token support in streak calculation (streak_freeze_tokens, last_freeze_reset_week in learning_user_stats).
  • Badge tiers: new bronze/silver/gold/platinum tiers for session count, mastery count, and streak milestones.
  • Analytics UI updates: Daily mission cards with claim action and freeze-token visibility.

Changed

  • Streak handling: streak computation can bridge one missed day when freeze token is available; token is consumed on activity-side calls.
  • App version: bumped to 2.5.0.
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureBtgrr+hdTfM0f4h9yPsjjpUL18GMWeoMLCyfuSk+f0aj+R2D8WpUaWos8YBBAdFtoTYGKeykyLd+Ww/8ckGusCAhrB8BiV2IDSKhna3EreyedyjQ7tI9KXrif6dvkN/8FQB3JsWSXyLujapZYGX0lsvVxwf6rzmLIsOhC5gAGD+aCHwsZDxzi2bWVr3iyMaQq1rqd7HxGMUZq6E67lRr2bKCXTK46QEX1TnGw/jtv7gMg5Kq0pG39oRgOTtpjNc/0VJR/KUw4PujcVYMaOyLOlJlNjN0bZjgsgyijcACSi5L3gF8/M7yKvHFwZK/MKaWozRsTu1fJBtvrblhCBeJvoAGanAW+1gCE4SyNzClU/ToY/utQGJWUFNhARuMHjmqkAiFjfoev57XvvKIlC3mBF9U8oRIL9uQY+O6D1d1oynxvyVLvCmMOzw36xzHcEr7Hygu6PJUEOS79QkROIBiivsT91fAgRyFnStnbrM+0Az8Jiyis1mRU3lzOOSEIomZoapMevwMgngBQUwfUPDeQysmaz5DdpmDNW6oHbjV8WwIItNkK2sZm4VoDMud+PyGbLBIG/3HBjQVS1Hrhc4cjpoBgVLASsIVh6qIzQAsJnpagFsxxAm9azNMiNm1vgvcqRUnVStk0hi4B4pO3pr76/K/h3m7Y7MJDu0n736r4Ww=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.2.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Fixed

  • Course Pool Access: Course members and instructors can now access pools assigned to their courses. Previously only pool owners and explicitly shared users had access, causing "Pool not found" errors for enrolled students.

Changed

  • PoolService: Extracted findPoolRow() helper, added hasCoursePoolAccess() fallback in find() method
  • 5 Services updated: LeitnerService, PoolService, QuestionService, TrainingService, TranslationService — all hasPoolAccess() methods now check course membership as a third access path

Technical

  • 5 changed files, 165 insertions, 12 deletions
  • No database migration needed — uses existing learning_course_pools and learning_course_members tables
  • Course pool access grants read-only permission (permission: 'read')
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignaturerHTnxsAiUtmAEk1HAE6AJN6jKvIyjYYyh58gOmB0JoBHJQyT5pFGKLy2mbGZ5ILVw/MqY1uD4/04K0b42tuj41OEKmXtIa53QcI2sBK0W3EeyAui2QmwXWlDUbaWYyo2BMarLHNt8WYuGcXM1erNLm0ucjsCAhCaW4QWwPeYU2j03TmyIhoi4MdUXXls6E0xJvLx3X8d/+lAJ5+JoFGXPkiK0celrDKW2jfiJP8t/GHWlutfA7HMmM6rzk9E+BgYMJgDLMZb93TVAs8TrTQDxQTjhN7zbWAd4l/QP1bbczF/tS8/wTW04yPJFoTr5HnNEHvsaPyB0oLmvvffxGmiwtKmyfTgf9vKBYARQGaj+khYGm2mKHTk5b4u1neqHJ92iO8CMPkj9ieg6AcZc2YV19URlTZMKbzn4lSxTfb4K5Gm+SXH/1heBs88fRNLaf5olKjuDiDExfxKlzzj+wiigXP9UF+va8sXj8JOz2TFMLQoPbTmnU1FOHgbtNJBcnnqe4SXz1eqRNSXQ2OGZW8bAmpo0oe4MK1CljeIIpIc+E5wjFZblWjw0NzIjoZEAkHg5SuHknsMDbcUqQPvCBJavpHM0L535Zmqpteh7H9kUw3H1BcD5hJyuAlL7FNKEtMO5TSOh7vFlCCNORllnXhi/i1oUL56gWGTbRUgiZs+fhE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.1.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • Free Text Questions: New open question type — users type free-text answers instead of selecting from choices. Fuzzy matching (case-insensitive, Levenshtein distance ≤2 for short answers, substring matching for long answers)
  • CSV/JSON Export: Download question pools as CSV or JSON files. Roundtrip-compatible with import — export then re-import produces identical questions
  • ExportController.php: New controller with exportCsv() and exportJson() endpoints, DataDownloadResponse for browser download
  • XP Streak Multipliers: Tier-based XP bonuses replace old linear formula — 1.5x at 3-day streak, 2x at 7-day, 3x at 30-day streak
  • XP Multiplier Badge: Visible multiplier indicator (1.5x / 2x / 3x) in Pool List when streak bonus is active
  • Open Question Import: CSV format question,model_answer,open and JSON "type": "open" with single model answer
  • Open Question in all modes: Training, Leitner, Smart Queue, Exam (textarea + batch submit), Daily Challenge — SwipeMode filters out open questions

Changed

  • XpService: getStreakMultiplier() and applyMultiplier() replace linear 1.0 + (streak * 0.01) formula
  • LeitnerService: All XP awards (correct answer, mastery bonus, daily goal) now use streak multiplier via XpService
  • UserStateController: state() response includes xp_multiplier field; answerChallenge() applies streak multiplier to challenge XP
  • QuestionForm.vue: New "Free text" answer type option with model answer textarea
  • QuestionList.vue: "Freitext" badge for open questions, model answer display, Export CSV/JSON dropdown
  • ImportDialog.vue: Help text updated with open-question format examples for both CSV and JSON
  • SwipeMode.vue: Filters out open questions (not compatible with swipe/tap interaction)
  • routes.php: 2 new export routes (71 total)

Technical

  • 1 new file (ExportController.php), 22 changed files
  • No database migration needed — question_type VARCHAR(20) already supports open, answer_ids TEXT stores JSON
  • QuestionService::isOpenAnswerCorrect() static helper used by TrainingService, LeitnerService, and UserStateController
  • Rate limit on export endpoints: 10 requests per 60 seconds
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureHyyUtHF7C32M+YZc15dn2kRI/DltaewXzt5Im3Zu3rPS12Q+ICmOK+AhT/TicLBFR3Zr21BjtyhcjcrFf5JmXIG9c65OEEyK2ApbXAa5HfFFhUbPIAjj+z2eG3F2+71DrFluPVg5imtKCJ1+zmZfNC3EFI+UFhUFPGor76KTJphQrT01qMaD21XuWlbr59lOI3PLuV+qENWBC2pvQMcBDTyfDEpanPR/NRZ9I31TqA5qj9d6su9UYxW0AO/21Dz6cTvUx6AeJksLyRjQsGEMZhvp5Lv3k3X4fiBp4q6j/VKtt/R89eCbNAjEcubZwYsAx6TA6gK1FtfOwO++7pU4EPzDlKFTBUpqoXJFvelF+SRg5h5oNJYU4zoJRCRV7IOIwliR//2G4Rh5bKenEELEhJ47l8lHfdw2vAkY1zMcOFRoTmda27VPD4W+uOBnd+7sXUSlPLCPRZYmZTMuFehgnPvLM8jN7e57ldBnzYw2AfSkS8vFmsIiT/TqhUknc7kFUAMD3Cz0PmofWWM+CbglCnD/HURBJW6FYX8dnA7fJhkyi7wSeC9nFc9TAU3jlVRiL/RA/wAmlxP2ZXJUk4JpuzxsdAwuBXkDRgnrmjnz31WdiqAI9f8HOPh9vc9ttbbMXP35llqMTa3T9NfnMZR0pkY0SKXcmSbAK3IHsv4dPI0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 2.0.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • Role-Based Navigation: Students start at Courses view (no Pools tab), instructors keep both tabs. App title simplified to "Lernen" for students
  • Onboarding Hint System: 7 dismissible info cards (localStorage-based) explaining Smart Queue, Leitner boxes, Daily Goal, Daily Challenge, and welcome messages for both roles
  • hintMixin.js: Reusable Vue mixin for localStorage-backed hint dismiss/check across all components
  • Mode Descriptions: One-line explanation shown below the active mode selector (Training, Leitner, Wahr/Falsch, Exam, Stats, Manage)
  • Pool-from-Course Navigation: "Back to Course" button when opening a pool from course context (instead of losing context by switching to Pools view)

Changed

  • App.vue: Mode selector filtered by role — students see only Train/Leitner/Wahr-Falsch/Exam (no Stats, no Manage)
  • PoolList.vue: Create Pool button, Share/Edit/Delete actions hidden for students. Better empty state text
  • CourseList.vue: Student empty state now explains that the instructor enrolls them
  • LeitnerMode.vue: Box labels improved — "Neu — taeglich wiederholen", "Lernphase — nach 1 Tag", "Bekannt — nach 3 Tagen", "Gut — nach 7 Tagen", "Gemeistert — nach 14 Tagen"
  • SmartQueue.vue: Better empty state ("Open a pool and start the Leitner system"), ready state shows priority sorting info
  • PoolList.vue: "Jetzt Lernen" → "Start learning", "Trouble Spots" → "Trouble spots" (consistency)
  • de.json: ~60 new German translation keys for all hints, mode descriptions, empty states, and v1.8/v1.9 UI strings
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureX22wHYsHapYtxGW+/4w3wuihpu0uy3Mi5x076ajJx5VbizQVWSF8uURw2bu+y/pQn4GZxinHK/JxHWqcCdU86MB3ThayzDX7ySTGx/z016eudjyAYAYGPx84KJ+ciGXFFiiS+QA7ebnPjnm/8O365ve4tVtv+7EqjHzOwntJnlT+GL9FAYiUS1z5UzFSuI02nNz3GXc3PG1NeWf3M1iAG9Q26zjv3n4q0mV1/GstjWvuJ5bAH/C8eNWtumTPjDq0/sBza15+DPzBtTuSDanieXSPi+EG2geKozRNe7fWBCWflVScnK38+9ke0vQsC+cgNMgsS+t9Qquv1IHQqbQLzEpFRq88LcWfar9VH0YFG9E1FaX2XrbF+59lZdM39NvwLrOCIfJFt7Qpnv/9Zkv4voezhaST8lKLI7FC/jCNhDcN6IpLPj3a8VuYsyMfu2vq3A1VDAyq7qb7T0l7T/FTRVQG2DlxZLnRMSMsQgFHmbiP2/5S553/N9JErAgrY85op8ucOCv83ysvUOYLVNtXHbPq9YyH+7GXDK0OsIvIfoK+gsaj/qcBt2x88FH1SlpY0VLDkZ7huBC21rvKTV0lT3sXyNo0H2HKPGR14Xm8DsN5GCIaljf0ErOJRfsxX9nUpDqU5aqWk8ABK+kyTldmwViOizIzc0uMMdeu0G7bUWE=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.9.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • AI Question Generator: Paste text (lecture notes, textbook excerpts) and let AI generate multiple-choice questions. Three-step workflow: input → AI generation → editable preview → bulk import into pool
  • AIService.php: NC TaskProcessing API (NC 30+) with TextProcessing fallback (NC 29) — runtime detection, no app-level API key management
  • AIController.php: 4 new endpoints — GET /api/ai/available, POST /api/ai/generate, GET /api/ai/status/{taskId}, POST /api/ai/import/{taskId}
  • AIGenerator.vue: Full wizard with textarea input, question count slider (5-30), language selector, polling progress, editable preview with select/deselect all, inline editing of questions and answers
  • At-Risk Early Warning: Instructors see which students are falling behind on the Course Progress tab. Rule-based risk scoring with 5 weighted signals (inactivity >7d, accuracy <50%, box-1 stall >60%, lost streak, <3 sessions in 14d)
  • At-Risk API: GET /api/courses/{courseId}/at-risk — returns students with risk_level (high/medium), risk_reasons array, last_active, accuracy
  • Remediation Queue: Focused practice on hardest questions (≥3 wrong answers, <30% accuracy, box ≤2). "Trouble Spots" button on Pool List with item count
  • Remediation API: GET /api/leitner/remediation (items), GET /api/leitner/remediation/count (lightweight count)
  • Daily Challenge: One random question per day from user's pools with +15 XP bonus for correct answers. Deterministic selection via crc32(userId + date) — same question per user per day
  • Daily Challenge API: GET /api/v1/daily-challenge, POST /api/v1/daily-challenge/answer
  • Migration V001600: Adds last_challenge_date (VARCHAR 10) and last_challenge_correct (BOOLEAN) to learning_user_stats

Changed

  • QuestionList.vue: "Generate with AI" button visible when AI provider is configured (checks /api/ai/available)
  • PoolList.vue: "Trouble Spots" button (orange, warning style) and inline Daily Challenge card with answer submission
  • SmartQueue.vue: New mode prop (queue | remediation) with conditional API endpoint, titles, and empty states
  • App.vue: Wires openRemediation event from PoolList to SmartQueue with mode="remediation", new smartQueueMode state
  • CourseDetail.vue: At-Risk section on Progress tab — collapsible card grid with risk badges and reason tags
  • routes.php: 9 new routes (ai: 4, leitner: 2, user_state: 2, course: 1) — 69 total

Security

  • AI Rate Limits: generate endpoint limited to 5/min, import limited to 10/min
  • AI Input Validation: Text length 50-50000 chars, word count capped at 3000 for LLM input
  • AI User Isolation: Task status checks verify userId matches task owner
  • At-Risk Access Control: Endpoint restricted to course instructors via validateInstructor()
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
Signaturedp2AlnFQwp1Cy8OCA/kE3gVqTvYhN9ORN/Oa86Lk0jjsYVLVFDHML+2ga4rlw5rjLTzojVtzB4BM1uJdyu/fRrdCUz9DhBBxIgNLNto8oIzu6HOYkE4TCB38quz5MGBbCVvZWnjmKb0ugYvzixvpy7ZPJnAWB5bwfj8C8AJqeEbbqqClkbjE4SAO9sxLfJbzDyWK7au4/It+H5gOO1yzXzUL6azX31M4acCwXZNI8AMRj6UHHqudtLsb9WgEC+cIHGEOfArbdIN9MDh1eDU3R17uuPjJw19g0KqZsc/NqGb/XuPUmHOHdcr8XzHDO4gYxp6FTEiMR8anCFogfEzpfrwGr/RAFzFBI0zfZ0+cbLHCNW3xrPVy/hOKGTlYXUHroalE6cvYdniSoXvQ09m6GlynsCauLZ8+hCb679ewlremqakW3E5RLtPL+xkP/ti0b8nev0Mdd9smil0YbrtPA6kJyk5JgllkPT3xhDDOtYrH+yrX2VNp5f4ypdWkPS8hM/SHId2W5q2D6K9XHVymde1aeA7AYPfa5/c62k7bvmwLbbNOWF4stKfq8MCA83RXASbfeHnAbdNbFltSPB9CT8oHBlBikwxk32iVKyoGEzuKZbAtZhC1x69ryyO0Uw5BAx6vexGDJUdgQb3PyeN5i2tV3Z2s3GCtHMjF+y0E8tY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.6.0
Release Details
UpdatedJune 24, 2026, 9:20 a.m.
Changelog

Added

  • ConsistencyCheckJob: Daily background job that reconciles learning_user_stats against source-of-truth tables — self-healing for XP, level, sessions, mastered counts, and streaks
  • Composite Leitner indices: (user_id, box) and (user_id, next_review) on learning_leitner_items for faster aggregations and due-question queries (Migration Version001300)

Fixed

  • HIGH: LeitnerService::answerQuestion() now wraps box update, stats update, XP increment, and badge award in a single DB transaction — prevents partial writes on crashes
  • HIGH: Optimistic concurrency control on Leitner item update (WHERE box = :oldBox AND correct_count = :old AND incorrect_count = :old) — detects and rejects concurrent modifications including Box-5→5 lost updates
  • HIGH: Box-5 mastery bonus (+25 XP, +1 mastered, badge check) now only awarded on actual promotion (Box <5→5), not on repeated correct answers in Box 5 — prevents XP/stats inflation
  • HIGH: Badge notifications and Activity events now dispatched after transaction commit — prevents ghost notifications on rollback
  • MEDIUM: Demotion fallback (updateUserStats()) moved after Leitner item update — recalc now sees correct box value instead of overcounting
  • MEDIUM: syncLevel() deferred to after transaction commit — eliminates lock contention under concurrent reviews
  • MEDIUM: Cache invalidation moved after transaction commit (was inside write sequence, could serve stale data on rollback)
  • LOW: Multi-select answer validation uses batch IN query instead of N+1 per-answer queries

Performance

  • Leitner box aggregations (stats, mastery counts) use composite index instead of full table scan
  • Due-question queries benefit from (user_id, next_review) index — faster Leitner review loading
  • ConsistencyCheckJob processes max 500 users per run, oldest-updated first — all users eventually reconciled
  • ConsistencyCheckJob logs progress and catches per-user errors without aborting the batch
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureKQnhIvGaYFr9qdGYI0R0k4GLBar4u3swxmHkT+MfcE1phTVpyAm8O9s/s1TIz7qIDG3TA7CcjVDPTh57M2WysEi8/Yu8OZgE2pjJbsTOOssjRtFoGUqzyU4AR74azP5w3f/wdgk0jiIEQvOV+4aM3fENSOSktpfeGZzhch0xHUfON9kS2h4x8+8AqH9M/xkPJvS0dUIUoGNGYsug8qu3HkRcBxsH7n4hYzg3ycx+eL8vWpwfhFOhAV70cpcHOrK/oVz0ra4tl06LdPDNWeCB3AYCHU8hcehaPr+VsNJmgOdBoRf7JbesAu5YOB25qy340wJ+XlLonXe03NTq+xoG67Na0laU9cs+j56X856ik8G0UeT4vIywTObfh0iKmtqk/pUKzv0YopTYl/iSPtU/hTquoXnSYbEALoyPyh9aJpgpKXEn7d7qyoMo5/9Bx1owdXNxIi7gGtue1MMrFTkCIEm3/SMFWmA4XBXSRt2zP4sEUKaE1L8STzO57jFtpzt/WfC47ccCRktkLEkXDIIpwYwjHQYol7P/luO1OqUwJVefFnC52Fc4tJDrabMIbLqfOqwUPip0o18zcqsaNpTVyc6Mw+VLgO6MkP6lmdWfq0nvpBpKMu5e7cySJoT440xsvCBTWIIcfQyYcMJGxFscSondANcYYAg/ixhtChuTd5U=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.5.0
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Added

  • Consolidated User State API: GET /api/v1/user/state — single endpoint replaces 4 separate calls (XP, streak, badges, progress)
  • Denormalized learning_user_stats table: O(1) reads for XP, level, streak, sessions, mastered counts (Migration Version001200)
  • Background Notification Job: NotificationJob (TimedJob every 4h) replaces widget-triggered notifications
  • Activity-App Integration: Badge unlock events appear in Nextcloud Activity stream with toggle in Activity settings
  • Timezone-aware badges: Night Owl and Early Bird badges respect user's Nextcloud timezone setting
  • XpService: Dedicated service for XP calculation, session XP increment, level computation
  • Response caching: User state endpoint cached for 30s per user via ICacheFactory
  • Composite index on learning_sessions(user_id, completed_at) for faster XP/streak queries
  • time_limit_seconds column on learning_sessions for Speed Demon badge support

Changed

  • BadgeService refactored from 430 to ~200 lines — XP/streak methods extracted to XpService
  • Dashboard Widget is now pure read-only (~140 lines) — no longer sends notifications as side-effect
  • AnalyticsDashboard.vue: 4 API calls consolidated to 1, with fallback to legacy endpoints for rolling deploys
  • countUp.js: Respects prefers-reduced-motion — immediately shows final value without animation
  • BadgeUnlock.vue: Added aria-live="polite" region for screenreader announcements
  • LeitnerService: Box-5 promotion updates denormalized total_mastered counter
  • TrainingService: Session completion updates denormalized stats via XpService

Fixed

  • HIGH: Leitner XP now synced to learning_user_stats — correct answers award 5 XP, Box-5 mastery awards 25 XP bonus
  • HIGH: Race condition in stats UPSERT — all INSERT paths wrapped in UniqueConstraintViolationException catch with UPDATE retry
  • HIGH: SQL injection in incrementSessionXp$currentStreak now uses createNamedParameter() instead of string concatenation
  • HIGH: NotificationJob memory exhaustion — fetchAll() replaced with streaming fetch() loop; N+1 due-count queries replaced with single batch query
  • HIGH: Level-race in XpService — stale getTotalXpFromStats() read eliminated; syncLevel() is monotonic (AND current_level < :newLevel) to prevent concurrent stale-level overwrites
  • HIGH: Stats INSERT fallback uses updateUserStats() full recalc — preserves historical XP, sessions, and mastered counts for first-write users
  • HIGH: total_mastered demotion — Box 5→1 transition now decrements total_mastered counter with updateUserStats() fallback for missing stats rows
  • MEDIUM: User state cache invalidated on every Leitner answer (not just correct), after share-badge awards, and at END of completeSession() (after all badge/streak writes)
  • MEDIUM: Migration backfill is per-step idempotent — each INSERT...SELECT skips existing users via NOT IN clause (safe for partial re-runs)
  • MEDIUM: Migration backfill includes Leitner-only users (no completed sessions but have reviewed cards)
  • MEDIUM: Frontend fallback triggers on any error (not just 404) for rolling-deploy resilience
  • LOW: Removed dead $totalMastered variable in LeitnerService (computed but never used)

Performance

  • Dashboard load: 4 XHR → 1 XHR for gamification data
  • XP calculation: O(n) SQL aggregate → O(1) stats table read
  • Notifications: synchronous widget side-effect → async background job
  • NotificationJob: filters to users active in last 30 days (skips dormant accounts on large instances)
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureOyK9vuzcblU3s5znaUXHm8WvkECMxcl4Jz5Xt1pNg4mTSvtO0R26bat9p9AMH75j70QPKj0FUJYI6jhObMU7X/lUu6b6+UhvO+RCtzO3/4OQ+gwULA4qSX4oWzmiG6obxmI51H0AnBOCwLb/JuVlbJJzDWuaSetL7fZX5oOEGoNuom7ZK3eoPovLG0d94fhldGhXBph8D6iUsySsmxuA1nU/nFE8kFTXAj89xHxPb1V6G4omNw+0K7mfjshDC++A44/zibOE/XvHYhLPmch/MvvHyPkJ7jMXzhOsSJYB+EAQMGEVMig4tOf6Cug9HFd3+adiHIPP+UpyI3R1lZP5YNSMsuvDviDHXI16P25mr87PZO6/XcOxbwRV+eG7aiRlRTtgvgbfCPQacIJfT+zzZzGytY2sa+htxiTis0sdQY2BdsdtlwD75NmlP37KEZFJuG6y+4TTV7kN6iTueYxfoYTcQ9irz82FrgNYTAgWAgNXJ6K9rnlHlBAc7ZU9+VT6tNzi39eJflwwLJKPjhPyBKiZ10ycIPwgJYLAtbTQdaYF7rmBDVM8xkS+KSjvyErD+vzV+lZRVy6G2zHZpiRhPSFHGc1ytWa3o180ukeo0fw2/fz29R/OcIxFupinMuAPeaoHE2yrGkHvg8yJSHq2Scen7Bp1AhNmiL8BIbSAHe4=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.4.0
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Added

  • Achievements / Badges: 14 badges across 6 categories (sessions, performance, mastery, streak, social, fun)
  • XP & Levels: Experience points calculated from sessions, Leitner reviews, accuracy bonuses, and streak multiplier
  • Badge Unlock Overlay: Animated full-screen overlay with confetti when earning a badge
  • Analytics Dashboard: Achievements grid with earned/locked status, badge progress bars, XP level bar
  • Session Juice: Animated score count-up (countUp.js), Personal Best detection, improvement vs. average display
  • XP display: "+X XP" shown on all results screens (Training, Exam, Swipe, Leitner)
  • Nextcloud Notifications: Badge unlock notifications, streak-at-risk warnings, due card reminders
  • Badge progress tracking: API endpoint showing progress towards unearned badges
  • New DB table learning_user_badges (Migration Version001100)
  • 2 new API endpoints: GET /api/badges, GET /api/badges/progress

Changed

  • Training complete response now includes newly_earned_badges, xp_earned, is_personal_best, improvement, average_accuracy
  • Leitner answer response now includes newly_earned_badges when card reaches Box 5
  • Share creation now triggers badge check for "Sharing is Caring"
  • Streak endpoint now triggers streak badge checks
  • Dashboard widget sends streak warning and due reminder notifications
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureYuFGppj5TQhlB5nd+fXqJurxGQUn7l0FFIqbWhXJMf9xPEi/g3wKdx7qukdK2ttz1Rf5xEbSdoLsGj3q1F7V5DZ+4+4GmMewS+MlYE4GA9ESFIA6lHLYxThICgb3G2lEGtImmp57tmGv7dzAiPqIH4pEdC0KT/iO08+VGy5R5s1gS5r36nHSrytf5C7JUBSSmOg6rVpUFIccDaXv8zn1f9sWWAZw4pRptDx+X29foqY2Zt4S/Swuz/SOGWKyfhhPw/x6RHjB3vnJIxx64Oi4Rom8BNdrU911RtMUFXLcnxq4xsv7VO2wGMkjNHegpL2yNr68yJSEm0bOjmVih06XMpJDNC0KN4S0vwnUkDIm1t9Dk1zxSPK5ba/PlQrTFxMVwratZsc1Uz0b/VqUBaZTu9oEBMpBm4CTrSf8XK29CNQ5EQXAzMSB0UUz0TqPpL2rI9kkcejJl0cololPd8N5R5jJpQ5miguwm6UjfTrIJzaizm6MAtXg+wx/DFfTyaSS936v1JEaRGIy4XkeQ9ZAAnSANlx9o8HTyLnWc7v6EsdkSo/7q88h7FaFUrlgkb3Gv7CQLBIJZ2jLtYa/2jv5gHQrg3c9unmIPLK8njpwS5+KgMH1V8LsyvraUW9ejzfmGv/BuN+reNyvToEoP8JIFPLs7SP1areV9svmkEnjIT8=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 1.3.4
Release Details
UpdatedJune 24, 2026, 9:19 a.m.
Changelog

Security

  • CRITICAL: Close training-session oracle — startSession(exam) auto-completes open training sessions on same pool
  • CRITICAL: Defense-in-depth in submitAnswer/submitBatch — suppress correct_answer fields via cross-session check
  • HIGH: Strip explanation field from Question API during active exam to prevent indirect answer leakage
  • Rate-limits on ShareController and TranslationController write endpoints

Fixed

  • Leitner feedback shows hint when correct answers are suppressed during active exam
Licenses AGPLv3+
Certificate-----BEGIN CERTIFICATE-----
MIIEAzCCAusCAhL4MA0GCSqGSIb3DQEBCwUAMHsxCzAJBgNVBAYTAkRFMRswGQYD
VQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxFzAVBgNVBAoMDk5leHRjbG91ZCBHbWJI
MTYwNAYDVQQDDC1OZXh0Y2xvdWQgQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBB
dXRob3JpdHkwHhcNMjYwMjE5MTIwNjM0WhcNMzYwNTI3MTIwNjM0WjATMREwDwYD
VQQDDAhsZWFybmluZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALi7
IyOTssJkAP8hllbLrwWmuhi3R8D6GZVB4inBQfUKu4lcShFuqanFgEipA17sikdW
ktyrbqV2MhZi7UJSPFarnUP8zf7uBLh79mvSb8dMoqorR+lqZpPbAj3a1oKkU7hc
MLjkYNKuFJlKiJnyJDGASzff/eZCdTjnk1q6w3w8C4NGVKlrt4Cq44NKi4jSfEL2
WMaifKaAnpQRRHR59BIggtGfu5SbThTyy8dHeeYSQ+x/0svIBiaRA4jrvqn0kMx5
HoLXgS3wuAZgjC40sI7Mw331Glj8GkAJcJyDJyQE/H/ZU4rKrvcaQHL/3MlmGTR4
/cjPfDhCzUYt5E/j/Y+TF43GUjNatzXf86aGvlxz9ajC8nfdwTCaa2ty6xnGh6dj
N0BqWTeRWOVWTrKXN9Ajxbhq9/wVI3uknRdIMZHM3kd9Gl47ha5SGxJh0vpUtayq
3ZMFnQjpQDHWq9rHDGwAa0i9PqvBOPRXerezJhFBtaYuNNT7IYB9q5pG3aFAG8Uz
YFKrbuHP9+23s+6ccH2nDnkwKCIg+lImmTjZfhIqiAuxHYPBeQvT0ObCaWW7oH6d
XucjHsA09ql3lN+lsa6OlFNSJugeRaZo8SDmM0YZgaIEmK/g217A8NKA+2g1rSmF
hkRLTrYFCrP3XPY62eq+jB5kL0A5IzwAJdd4WlNrAgMBAAEwDQYJKoZIhvcNAQEL
BQADggEBABNbpoehsB5WXmkJgDYPpP+z/YksqVeVg0k+79iwAJzaIyISkfqSkt5l
DGKcfDPYOCw+nhrWZWyI8mS6noWvYVHK/AWbSI4uuBkaC8t8VgpBvBlRDyui1ewq
11fne57P9cQNHwk0PGkjvsAhykq/85AXks1iIKP9sjzrmrv0s61tvbE5LJtulWKY
SbkzDiFGlQFAAKIm9z6UUENNMty8QkBMFrLd/MxSEfY0x2YcOEx8ptB07AMNPzwW
t0k6ykD59OzU+t1SsSqWEih3wxP8FkO5XSxrFlXK/L5f55NpQeel3vh57a5fGQb6
UI4RLlKb/qz3o1Bj1qdKBp32qx6G6/Q=
-----END CERTIFICATE-----
SignatureajCD73Wim+3CcebKFs72Co993wGojtfsPy8b+Gde+kx+EhPmDpzYEGdgbb54ZBqpDmJAroHIbaqZa0FfK8SwvFwC2JZ7WOp4xwOAjp+V7ruKvXu24MmzsH2wUp9U2ec+sO+EG9+ebsDELjaFEf2m6EMbRhw+/QUzsE51kaA10RaTwoNfTu29LLraGaEEFVWh9R5x5jbqNj9Wdvfq/vNNTp/XTm4SBLnAsD+7ymyMucfwHyn4ZYGspXdNd8v0HS6ZRe3JjtcOd+IjwEebZ4b2KPBCrnGs8sr+6zer7FItbLUOq2MxxdhaOml1yTQIPzeyuJPnFvmLJOw0FHJvsn/ZMMjCpByVSrf2MBua3yW4DcB/Z9m3yBwM3lQ98QUQDgmAfHwU1zG3P5ijvxFvaOz5XW3sFsCaktXfGtFfrMgUgbN8aee/7hZkA36Vi+zIUsbs+eExv5QvDn6bG21DrE8022vnfZ7gY9HFQESVMGsnZc9yqdeGJn9F09jScGcZvyinjtUUCe2/SRfjPHHa9JWvZ84pQuO+0OyAbvIEOlwz64r5qNatt47yoxAEzEUsu8oFQP2s+PX/Ni/yyEIWbH4LaK0qCPTulJKvujaq1fukSchT+9UMBVKxEebC672M1LtejtJnJfZVUp6WuAecz2QmBU4Va6WFDVLSkbAiz6ON/Jg=
Signature digestsha512
Dependencies
Required Nextcloud versions >=29.0.0,<32.0.0
Minimum Integer bits32
PHP>=8.1.0