Skip to main content

Learning - Releases

← App details

Nextcloud 35

Learning 5.10.0
Release Details
UpdatedNov. 13, 2026, 9:25 p.m.
Changelog

Reported by alphalib-g in GitHub #35: a small organisation that certifies its people every year found that learners passed and never got a certificate, that learners enrolled by typed user name could not see the course, and that learners could not open the course materials.

Administrators, please read: certification follows a new rule. - The certificate is issued right after a counting exam. Before, it was only issued when the learner opened the completion tab, and that tab was hidden unless the course summary was released. - Every required pool needs its own passed exam. With required pools, each of them needs an exam started in this course's exam mode at or above the course's Minimum score (%); the best attempt per pool counts. Without required pools, the best exam of the course decides. Leitner mastery no longer counts towards the certificate. - Exams taken before the update count under the new rule, including the former 45-question "Practice exam" card, which always counted as an exam. A learner who already passed but lacked Leitner mastery receives the certificate the next time they open the course's completion tab. - Practice exams and the new pool practice runs never count. - Instructors of a course get no certificate in it (owner and co-instructors), also not through the student view. - A recertification counts only the exams of the new period. The period opens once the certificate has expired and the grace days (default 14) have passed. Before, last year's exams renewed the certificate without a new exam. - Every PDF and Markdown file directly in a linked material folder can now be opened by all course members. Link a folder that holds only course material; the home folder is refused. - Exam slots are removed (Administration → Exam Slot). The setting said learners could only take the exam during a slot, but nothing on the learner side ever checked it. - A repair step corrects member ids. Course members and pool shares stored with the wrong letter case (for example "Alice" for the account "alice") are rewritten to the real user id. A row that would then duplicate an existing one, or that names the course or pool owner, is deleted instead. Rows of deleted accounts are left alone.

Certification

  • Issued right after the exam. The exam result screen judges a counting exam by the course pass mark and says "Certificate issued" with a button to open it, "Pool passed — still required: …", that the pool is not required for the certificate, or the pass mark and the retake rules. If the certificate check fails, the result is still saved and the screen points to the completion tab.
  • Pass rule per required pool (see the note above). Only required pools that are still attached to the course count; if none is left, any counting exam of the course decides.
  • The completion tab ("Completion & certificate") is always shown in a certifying course. The course summary switch now only releases the statistics. The tab shows the pass and certificate state (issued, expired, revoked or issuance failed), the required pools with the best score, the pass mark, the validity, which exams count, and the retake rules, and offers a retry instead of a blank tab when loading fails.
  • A failed issuance is reported, not shown as success. Without a usable signing key the learner reads "contact your administrator". Under Participants → Completion & certificate, instructors see who passed without receiving a certificate and whether certificates can be signed at all.
  • Recertification counts only exams of its own period (see the note above). The same applied after revoking a certificate: exams from before the revocation reissued it at once.
  • Instructors are never certified in their own course. The rule is still checked and shown, but nothing is issued or written to the audit trail; the completion tab and the result screen say why.
  • An expired certificate is named on the result screen, with the date and the note that a retake counts once the renewal period has opened after the grace period.
  • The certificate settings warn when the course's Exam mode is off, because then nobody can be certified.
  • The certificate notification opens the course's completion page.

Enrollment

  • Enrol people and groups by search. Under Participants, search for people or groups (Nextcloud's sharing rules apply, at most 25 results), choose the role and add several at once. Choosing a group enrols everyone who is in it right now; people who join the group later are not added. The result names who was added, who was already in the course, who was not found and which group was not available. Existing members keep their role, and a different role picked for them is reported as not changed. One step enrols at most 200 people, 20 groups and 1,000 accounts in total.
  • Members are stored under their real user id. Typing "Alice" for the account "alice" stored "Alice", and on PostgreSQL that learner never saw the course. Pool shares had the same problem. Existing rows are corrected by the repair step (see the note above).
  • Clear messages instead of "Failed to add member": unknown account, already a member, not an instructor of this course.
  • Group privacy: when sharing is limited to own groups or user enumeration is restricted, a non-administrator can only enrol groups they belong to, and a typed account outside their groups looks like a missing one.
  • Enrolled people get a notification that links to the course (not when you enrol yourself).
  • The course form's group field works. "Enrol all current members of this group" enrols the group's members when the group is set or changed. The group is checked before saving (it must exist, be allowed for you and have at most 1,000 members); otherwise the course is not saved. Saving the course again enrols nobody, so people removed by hand stay removed. Before, the field did nothing.

Exams

  • Exam cards follow the pool and the course. The exam has up to 90 questions from the pool (all of them if it has fewer), 1.5 minutes per question and at most 120 minutes; it is disabled on an empty pool. Before, every course offered fixed CompTIA cards (90 questions, 90 minutes, 720 of 900) and a 45-question "Practice exam" that in fact counted.
  • The CompTIA 720/900 scale is only used in courses without certification. In a certifying course the result is a percentage against the course's Minimum score (%), the same rule as the certificate.
  • New "Practice run" card per pool, shown only when the course offers practice exams. It uses the course's practice settings (questions, time limit, pass mark, attempt limit, cooldown), ends with a review with explanations and never counts towards a certificate.
  • An exam started from a course is composed by the server: at least 45 questions (or all eligible ones if the pool has fewer), without a question-type filter. Before, a shortened request could turn a single correct answer into a pass.
  • The exam setup names the pass mark when the exam counts; texts no longer promise a simulation of the real CompTIA exam in every course.
  • Exam slots are removed (see the note above). The exam date only drives the learners' countdown and reminders, and its help text now says so.

Course administration

  • Learners see the course schedule as a timeline on their dashboard. Before, they got "no schedule" because the schedule was readable by instructors only. The timeline matches entries to pools by the pool's chapter key or name and shows a load error with a retry.
  • Schedule editor: a chapter ID column with suggestions from the pools' chapter keys; a repeated ID is named before saving, and a refused save shows the server's reason.
  • Every learning mode switch says what it does. The Leitner sprint setting only shows while Leitner is on, the adventure campaigns only while Adventure is on. A note explains that gamification is a server-wide setting.
  • New switch "Show simulator tools in this course" (on by default, so existing courses keep their tools).
  • Saving the tools, the sprint setting or the Talk room no longer stores a mode switch that was changed elsewhere without its own save.
  • The student view shows the course the way learners see it, without the instructor tabs. An instructor enrolled as a learner in a colleague's course no longer gets the Pools, Topics and Knowledge tabs there.
  • Help texts for maintenance mode, topics and chapter keys describe what the app actually does.

Materials

  • Learners can open the material files: file names are links. Only files directly in the course's current material folder are served, and files of a view-only share are not.
  • The extracted text is no longer sent to learners. The document list used to hand every course member the full extracted text of each PDF and the extraction errors; status, errors and paths now go only to the course's instructors.
  • The owner picks the material folder with a Nextcloud folder picker; the page says that course members can open every PDF and Markdown file in it, and the home folder is refused. Re-picking a folder that was typed in no longer registers its files twice.
  • Extraction is explained: with AI off, extraction status and buttons, the Knowledge tab and the learners' Swarm tab are hidden; with AI on, the page says that extraction only feeds VirtuProf. Without pdftotext the page and the admin settings say so, and "Extract all" leaves PDFs alone instead of failing them.
  • Learners get their own empty state, and "Learning materials" comes first in the learning space when a folder is set.

Fixes

  • Database errors when starting a training or exam were sent to the browser with the raw driver message (table and column names, SQL); they are now logged and answered with a fixed text.
  • A running exam resumes only in the course it was started in. Before, an exam started outside a course or in another course sharing the pool was resumed here and counted for the wrong course or none. Now the learner is told to finish it where it was started.
  • Practice attempts are no exam scores: they no longer appear as a pool's exam score in the learning profile and earn no exam badges (quick thinker, exam ready).
  • A schedule that failed to load can no longer be overwritten from an empty editor.
  • Course materials that failed to load show an error with a retry instead of "no learning materials", and no folder, scan or extract controls.
  • The dashboard exam countdown skipped every exam date saved with a time.
  • Translations: the exam-mode hint was English in French, Russian and Arabic; French showed "Group" instead of "Groupe".
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-----
Signaturep1mTaRy9uh56bfMkWJoABvbRoR3wPat7BYtozHJHNhlitCAJTgX4d3t20IvYgB4jU5mbrlB2qAaQBlRjwDlyF7XveAJBqlrATVnocWVrX4B4In4pbot5i0RFpuw/i+TAdkUas6EtLIX9QfPH/kxBYMaxuZgoEjv2T4XwzP04z5fE/M1uTwsBwwMXfyPy8aLj8JT5Ca6HFIZSMyyylBqrSvMt2LIm7H/+BeMYYWSBckSkgNLaas5uDdxAIA1HauIS887udrCscVyYt/+GZwgmJ7oumkzsm1CPGndrykp6X6p3nXkrDp23NXP337ipW9pi+2w9GKwrhiy35TAcTj5+k6RWU2nKok9FFmjHVJ+4fwHvLOyWfX549A2Or3gmEBJmhV3E2EQY199M3EhNzyn5zJb6mKUcpXJxeukjWB64n1nlSh9JVlD6DOufpI5XZUyIlTzu0xl6uFsYFaFaXEIudw9S/DsM9lHRkl23To3u54EM7J+oYPwU3gdxqXk08GWdtlsuz3onenx9/cs2OTohepCl8JsB8ss5jUiYxAOHTBp6kLYCDfQM+8lXnVCbJb4XgwUSlzqkDlnZy+XLSOd5CBgNp2ikWlRSV/pH9vw4/wWfjYjWbIK2yQMqKY3yFTZBLAoL411eM6AMM1QVs5iugwtl4N+8WTDVMSFkNJaFXTA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.9.0
Release Details
UpdatedNov. 12, 2026, 9:33 p.m.
Changelog

Administrators, please act after the update: enter your operator details. The imprint and privacy pages no longer name a fixed operator. Enter name, postal address and contact email under Administration settings → Learning → Operator details (imprint and privacy notice). Until you do, both pages show a neutral note instead of an operator, and Learning no longer sets the Nextcloud imprint and privacy links (Theming). Links that already point to Learning's pages are kept and lead to that note until the details are entered.

Fixes from a full code audit of the app.

Security

  • Learners no longer receive the answer key with the questions. Correct-answer flags, explanations and interactive (PBQ) solutions in question lists, search results and translations now go only to the pool's owner, users it was shared with as Can edit, and the course's instructors. Learners and read-only shares no longer get them. Practice modes (training, spaced repetition, bot games) still show the correct answer after each answer, as before.
  • Exams no longer leak solutions through side paths. During a graded exam the question APIs withhold the solution of every PBQ type, including the grading data of command-line and multi-panel PBQs, and the cheat sheet leaves out pools with an open graded exam.
  • Adventure skill checks only use questions the player may see. They draw only from published pools the player has access to, accept only the questions they offered, and neither draw from nor reveal answers for a pool with a running graded exam.
  • Duel and Gameshow accept only answers to the current question, and the Gameshow sprint time bonus is measured on the server clock and capped at 500 points.
  • Course archiving and reading archive snapshots are limited to the course's instructors and Nextcloud administrators.
  • CSV exports of at-risk learners and the class summary neutralise cell contents that a spreadsheet program would run as a formula.

Changed

  • Operator details are an admin setting (see the note above). The imprint and privacy pages use only what the administrator entered.
  • Bot mode in Duel and Gameshow grades your answer on the server, because the browser no longer knows the correct answer.
  • Exams: answers that arrive with the automatic submission up to 30 seconds after the time limit are still accepted, and the exam submits itself 5 seconds before the end. Before, all answers of a timed-out exam could be lost (0 %).
  • Gameshow: when the 15 seconds run out, a missing answer counts as wrong and the game moves on for everyone. Before, Sprint and Elimination could stop on a question nobody answered.
  • Required Vimeo and YouTube videos report their progress. After the learner's consent the page loads the provider's player script, which tells Learning which parts were watched, so such a video can be completed and unlocks the course quiz. Before, it could never be completed. Nothing is loaded from the provider before consent; the privacy page lists the new category.
  • The nightly XP consistency check never lowers XP or the level. It used to reset XP from single training questions, streak bonuses, Leitner reviews, missions, goals and Gameshows overnight. XP already lost this way cannot be restored.

Fixed

  • Team lead dashboard: members with a valid certificate (without expiry or expiring in more than 30 days) are no longer listed as missing; expired certificates show as expired, with a reminder button; the newest certificate counts. Due and expiry dates show as dates.
  • Exam mode: Enter on a focused button or navigation dot activates it instead of skipping the question, and keeps its hotkey meaning after a mouse click; the next PBQ in an exam starts with its own empty answer; pool editors keep the solutions and their explanation translations during their own exam.
  • Required videos from Nextcloud Files send only the newly watched seconds, so they can reach completion.
  • Unified search works on PostgreSQL.
  • Course material search (RAG) keeps Cyrillic, Arabic and accented keywords and keeps the text directly under a Markdown heading; a sub-heading directly under a heading names the chunk.
  • Imports: pool_name is read as the pool's name; question type multiple_choice becomes single or multiple choice by the number of correct answers; the occ dry run counts only what the import would store.
  • Adventure mode: skill-check questions work on MySQL/MariaDB; a new skill check starts with an empty answer batch; bot-correction scenes no longer list the correct option first.
  • Games and leagues: a duel forfeit goes to the player who stayed; co-op players who were ready first leave the lobby when the game starts; ranking PBQs are scored in the spaced-repetition queue.
  • Leitner: switching the language during a review keeps the queue and the current card.
  • Course feed: "Load more" loads the next entries.
  • Exam reminders find exam dates saved with a time.
  • Maintenance mode can be switched by co-instructors.
  • Material scan detects duplicates per course instead of across all courses.
  • PBQ builder: applying an existing PBQ keeps the fields the builder does not edit.
  • Navigation: a pool opened from a course keeps its course context; the app works when Nextcloud runs in a subdirectory or with index.php in its URLs.
  • Calm display: the switch shows ON when animations are off, and the setting applies after every page load.
  • Learning profile: the history trend reads sessions newest first.
  • Onboarding: "Skip" on the Jumpstart screen declines the selected starter pool.
  • VirtuProf: auto-read and chat scrolling react to new messages.
  • AI errors of the note generator and the weekly plan are reported by cause (not configured, rate limit, temporarily unavailable) instead of a generic server error.
  • Admin settings: an exam cooldown of 0 minutes is shown and saved as 0.
  • Tools: the subnet trainer accepts IPv6 answers in any valid notation; the VLAN frame preview puts the 802.1Q tag after the source MAC.
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-----
Signatured0EiRXMmplgSSmtW8lYHyMhlK+d+z3FllLYfd6W5/KOI+d4zZ7TEvr3npFdou+eaiRCL+8VWyqTY5yjMt04R5txLiL37+A5ENjxDu1g5AmDsJjOqZiKI+KY5YqFP6tZUGOvd2t4stZVkdt8EFI6Wt7vyhLeMXiCTFU3aj6m/SXhf2E9TDiQT3n5fJI9CQcz1V7RHuWCQHQfrjBLbobxhDK5OiqKYdl0adhUJv/Ob2QZAm9sreFAxiN7o5ugYJ3UNePLmXF4aPqzvcAuWKwCmCRnh0EOBpx1VSucwj5eI0h4epBmSyZoitun3IBSMw4xhxFOdMgvhMejcfbEp+y8VXEGkIxZkCCHg3Xrcj+R4T1KX2YasObMlJhNhvA40gr+DLSK18y3UZvHMiOtwWKHzuhzESBJ0u8ZCRPaQr2kkg6yqwHRK7ZSmHJQioLnim6/F40xwUk6qmo4c5y8tiwtJfE9nCRsxwlwZ5evDa8i5d+LfNQvifILYt8OQTpBfee8qSeIKendI+wgOTVZ/mg30I3RIQV0GMjI+dThfzpK5/IfJooifQnQKu9H/LjXovMUUhNIY1wl8tVaO9MNMlGBpiZ/wb2AfrsYAPHk3vLnrLEwa8KDC8JZqNJZucqdpaMuegSrGE+yi525CQBQiCl1bokb4DVx9eQjt31D7JVS7aFU=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.8.0
Release Details
UpdatedNov. 12, 2026, 3:34 p.m.
Changelog

Reported on GitHub #34 by a self-hosting administrator who could not find where questions are added and whose JSON file did not import.

Fixed

  • Pasted JSON was read as CSV. Pasted text that is valid JSON now switches the import dialog to JSON by itself; broken JSON stays on the CSV tab with a note that quotes the JSON error. Uploaded files are read by their extension (.json/.csv) first, so a .csv starting with [Network] … no longer goes to the JSON reader.
  • Many common JSON shapes were refused or crashed the import. The import now also reads answers as plain strings (options, choices) with the correct one named on the question (answer, correct, correct_index counting from 0, correct_number counting from 1, a letter or the answer text), the documented pool file with one pool, and numbers where text was expected. A marker that names no answer skips the question with the reason in the import report.
  • The pool format lost its metadata in the interface. The pool's chapter fields, exam_key_prefix and the _meta defaults now apply to the questions in the order question > pool > _meta, the same rule as occ learning:import-pool-json.
  • CSV with line breaks inside quoted fields (as spreadsheet programs write it) was torn apart. Quoted fields may now contain line breaks, the separator and "".
  • An export did not come back complete. The JSON export now carries difficulty, chapter, handbook and exam key, and the subtype and configuration of interactive questions (PBQ), so it re-imports without losses through the interface or occ; ranking PBQs can now be imported. An exported multi question with one correct answer stays multiple choice. The CSV export (also the course statistics export) is plain RFC 4180 now, the format the import reads, so a backslash before a quote no longer breaks the re-import.
  • An empty pool was a dead end. It now opens in Manage for everyone who may edit it, and Training offers Add questions.
  • Wrong answers could be marked correct. A correct answer after the eighth was cut off before it was looked up, so the first answer was marked instead; a correct-answer list naming the same answer twice (such as [1, 0, 1, 0] or ["B", 1]) was silently merged. Such questions are now skipped with the reason, as are questions with more than 50 answers and correct-answer lists longer than the question's answers.
  • An unknown question type failed the whole pool. In occ learning:import-pool-json and the pool import, the dry run counted a type such as multiple_choice_extended as importable, then the real import refused the entire pool, valid questions included. Only single, multi, open and pbq are accepted now (any letter case; true_false reads as single, the historical multiple as multi). Any other type skips that one question with the reason, in the dry run, the import and the question import of the interface. Creating or editing a question refuses an unknown type; a question that already carries one can still be saved unchanged.
  • Out-of-range values. A difficulty longer than 20 characters failed the whole import with a server error; it is now cut to 20 characters. A chapter_order outside 1–9999 is dropped, in the interface and in occ alike. Answer texts over 2,000 characters are still cut, but the import report now warns. A CSV header row no longer counts towards the 500-question limit.
  • _meta.default_question_type: "pbq" no longer turns a question with several correct answers into an interactive question. Interactive questions accept text, question and frage as the question text, not only question_text.
  • Import errors from the server are shown in the reader's language.

Changed

  • The Manage tab follows the pool's edit rights. The pool's owner and users it was shared with as Can edit see it, also without the instructor role. Creating a pool still needs the instructor role.
  • The student view is visible. While it is on, a banner on every page says so and offers Leave student view; a role that could not be loaded is reported instead of silently showing the learner view.
  • The import shows a preview, for JSON and CSV, of how many questions can be imported, and why the others cannot, before anything is saved.
  • occ learning:import-pool-json reads an export directly: a plain question list, or {"name", "description", "_meta", "questions"}, becomes one new pool, named by the new option --pool-name, else by the file's name, else by the file name. The command now also imports PBQs, and a question's own chapter wins over the pool's.
  • Manual and import guide describe who sees Manage, all accepted import formats and the round trip export → import.

Added

  • Import pools in the Pools tab. Instructors can import a JSON file with one or more pools (the format of occ learning:import-pool-json, also a plain question list or an export, which becomes one pool named after the file) in one step, each pool owned by them. A preview from a server-side dry run lists every pool with how many questions can be imported and why the others cannot, before anything is saved. Each pool is saved on its own: a refused question is skipped, a pool that cannot be saved is rolled back completely, and the other pools stay. Up to 50 pools and 5,000 questions per file, with the same file-size setting and rate limit as the question import. New endpoint POST /api/pools/import (instructors only, dry_run for the check run).
  • occ learning:import-pool-json and the new import share one implementation, so a file gives the same pools either way. The occ dry run now checks every question and lists the ones that would be skipped, and a pool that cannot be saved is rolled back as a whole, with the technical reason written to the Nextcloud log instead of the output.
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-----
SignatureVrC4hI1zjAahOe8f5+nyNpqhRuV8zH8by5r8MhjccizM5rWKNF4I4J2ENiI4sh/Ja12panTqJuyWZc837YRnxukcjbMjLj1KxLoX0ZYXas8a37pkoJ8sIkXmmby8mGXsFjuQsvuxN2cvkMU/uU07gxxg3myFbQge3b+ND92bHCO9tl7IGyyCVTNIwtdQAL4ldaS6fONrUxlXMOA0c+FEEWGqhAHBNMwUX7ChacWOM446YR4jZTzEKckL5BBTqPjtg94W9eX8/vapA5ZRVvbfoAtKXWkMULpGGHcEvwwds+hBZ2bxvOuyqcJdCEgRVWD+Xp/aBVhDTqiq+4+d738sO8ILhFxchpcOIADHbcPyfO2RldGQZMxA8pD9CJu1dKHB+9mOg3oar9Dx4wF5dZGjYqew+/jo12pXSdSDc5PRaQxvbcYSzPQZwkl1118Tw9yFs441sFhCES7SQS7/fnM3bx1JECwXMA3O9oJq+ysQRsl2Nk1mI3+UvtQAXr5RgD26x3/F1HOWt99g92jEcOpPFFU1ZvfYYHO2G5NM9z1akY1ibgqCHsxETZjamI1KulFEWmhcJ9iYN9TXTP7WuO6vnqaj50ZjXWvXtwGfn9yFB/jjyYrQw6tdRIhSeZyqGtQeJdU5qMWdyZ2hl6RSl6n+7UiCP3Ie7bIuEUTBjcwKDRI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.7.1
Release Details
UpdatedNov. 4, 2026, 8:18 a.m.
Changelog

Fixed

  • "DevCloud" no longer appears in the interface. The name of the author's own server was hard-coded in the onboarding ("Welcome to DevCloud!"), the imprint and privacy pages, the personal settings and the VirtuProf privacy hint, in all six languages. The texts are neutral now ("this server", "the learning platform"); the PWA guide no longer names a fixed server address.
  • The streak and user state failed on SQLite (/api/v1/user/state answered 500): the query used MySQL's FROM_UNIXTIME. SQLite now uses its own date function. PostgreSQL and MariaDB were not affected.
  • Audit checkpoint anchoring had a default host. Without a configured Forgejo URL it fell back to the author's server. It now skips the anchor instead. Anchoring stays off by default.

Changed

  • Notifications (toasts) come from @nextcloud/dialogs 8: accessible, and shown at the bottom start of the page.
  • Development tooling: PHPStan 2, PHPUnit 13 (tests only; the app still runs on PHP 8.1+).
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-----
SignatureHV8IxQAN0P9065NUlpA9bAX9eDV/BplS4KgVDTjS7map+w8Z/de0MYkfQ7uM/ypq6yVCSkO4u/JxyZO50joNh6sxAnfjZq/JJepaAeycRr55qrwxBRM4+/ScP5tVs+RYcZl8ZU0+eKWS/mcevp77wzqoPEaxqOe6p7ysKZjUyCTUfnLp+B8pI4fyt13ji/DC5meFijLB23brN0SMhDEhHxUh8ScKk+PRpSgzMzyNIUQALBiV7aB+TIYLPl9pbVKkHaNvt8PRVYyPwJgypdw1FxgnNR+X4wFlGK2mTjsuKroPKjkyzYkIUHokxNiJasX8O1NfrDaZOgOigCsM38cTXB1Vg/Xw04znmM4AYGg7q8u4Pze/DvDWNI4r8mGQg7tbnCUiwLhhD9iQauIlFadqjLumX+8a4z+KVOZVqGHbgNNrjXeZJTonIvQcjvhFUWFTbtGReO84IxQUbYaVlVo4pjxP7QB6v6yRLpr/RufmqzfGZ9qolSc4AVlyoM1SlBNHvYMaWdCzaTeqNZMcQlB5q5LqRXMm0N7RtkxzK24BUJiegAJ+9UWybizbAiYKLHFXXnv09XjHhUTgFXSviU2EfD1zscQuBvgZDwXR7M8wr5lbyWsVPG8SYrJjA2MV8glpg5PbBHzCCBYE/+QNchr11ZSLBzI3kJxGJhG8NnK/9sM=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.7.0
Release Details
UpdatedNov. 4, 2026, 7:38 a.m.
Changelog

The AI features (VirtuProf, weekly study note, learning plan, course narrative, story narrator, pool generator) work as before, with Gemini or a local Ollama server. What changed is how consent works and how a request leaves the server. No new AI providers yet; they follow later.

Changed

  • Consent names the recipient. A learner's consent now covers the current consent text and the AI recipient the admin has configured (Gemini or a named Ollama server). If the admin switches the recipient or the consent text changes, each learner is asked once more before anything is sent; the dialog says what changed. After the upgrade every learner who used AI sees the dialog once, headed "revised".
  • One way out. Every AI request goes through a single gateway. Right before sending it checks again that AI is switched on, that the learner's consent still holds for this exact recipient and that the limits allow it. Consent revoked or recipient changed in the meantime: nothing is sent, also not by the weekly job that may already be running.
  • Real revocation. Learners can withdraw consent in their settings. It takes effect at once.
  • Limits per learner (10 requests a minute, 100 a day, 3 at the same time) and a daily total for the server, counted in the database so parallel requests cannot slip past them.

Added

  • Accessible consent dialog in all six languages, with keyboard focus kept inside the dialog and right-to-left layout for Arabic. It is the only place where consent is written.
  • Connection test for admins: sends one fixed test message to Gemini or Ollama, nothing from any learner.
  • Data export lists the AI consent and the stored chat summaries. The public privacy page names the configured recipient.

Security and privacy

  • The Gemini key is stored encrypted; the upgrade moves an existing plain-text key over.
  • Requests go only to the configured address: no redirects, no automatic retries, the address is resolved once and pinned. Link-local addresses (such as the cloud metadata address 169.254.169.254) are refused for every provider, also for an Ollama server in the local network.
  • Frontend dependencies updated for known vulnerabilities (Vue, axios, DOMPurify via @nextcloud/vue, PostCSS, nanoid, form-data, among others).
  • Prompts, answers, keys and provider error texts no longer reach nextcloud.log or the audit table. The upgrade removes such content from older audit rows.
  • Contributions of other learners are only put into a prompt when the recipient is a local server.
  • Account deletion also removes the learner's AI limit counters.

Upgrade

  • AI tasks left over from 5.5 are cleaned up; old task IDs report as expired. The upgrade stops with an error instead of continuing half done, and resumes after a crash.
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-----
SignaturehCSbN80e31JkExc32f+TDb7MdXg+K34/KfGKbWb92DBagD8pLGHsnhBwvFxTenCDEXBm2eobnLcTe6MEQaBSL+OIs41oVB1An+tdRLtnKjMVtMcXs9PA3PPQORljd+Vvh2ptodCa3Z+UTsvhiwZN4G1ESjzMVt2Ql0Po0FFzgmFVFqkPLVPGnbyzxrQZ2DNZWgukzq3DItHlRFa83i0KzXHIgKoJB9kKji9WrQWZjjIlHeCcK8ZgvAO9iYqDf+CrnkcdaWY80lny9lZj2J7oD3YOY/OBsU1BpcAl6E11jDPEHGwgHN/mGZ+WQMr4JqsXkEFxeqLDiC6zktHVCoKPAdpcOmqiUWIvAR5rfNIIA92p95rFbZWsaW2VwPahwKZryUcCHwcLIhxCKCw8M958lga0DoBxMQwsyo6n9j6DYnHJrtnfVZqlOMTv4kzJ1PrfR26o5HWN/k0TUI94H3AW5+1N9BqkIJ9zICVzQCm8oM9kXQwUJ5iafdmiKctoCCz8mwxVFUIHq0se9lpRW4bDEVSVQo1TmAIwPoRWwW1l5zgZpJM2XlBawyPNBDKILKrB7D+mTW8R6lh659E3qNEowkE88AaHIWBRipMdSh/ALxvBM5tneZC8RUOhIzaZITtLUSXB49C0AdPV4eoeuHzum5xmN8hbEAhgnY7z3a7bOpA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.6.1
Release Details
UpdatedNov. 4, 2026, 6:34 a.m.
Changelog

Fixed

  • An exported pool lost its multiple-choice and open questions when imported again. occ learning:export-pool writes the question type as type, but occ learning:import-pool-json only read question_type. Every question without that key was treated as single choice, so multiple-choice questions (more than one correct answer) and open questions (one model answer) were rejected and skipped with a warning. The command now reads question_type, type and typ, like the pool import in the interface. Without any type, several correct answers make the question multiple choice, as the import documentation always said.
  • % and _ in a search word for course material matched too much. The keyword search behind the AI assistant's course material passed them to the database as wildcards, so a search for 50% also found 500. They are now matched as plain characters, like in every other search of the app.
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-----
SignatureqlmJFTEKJuojHnQtpGBmvWQ2Kcl46WMRiKoYOT30QA9Wye0kTDNBmuWtr6gc6MI4mK3GzRCcNd57B3+cfpRJZnyMrKG8ZtiPxwnYfnb7mfxjUk+4k86qnoqb1lwFOXhm1NOsnEBPm5Zkaa+RjpsWsJhBUXamyZCuDiHt1xVqzew/5EW4qY9dOHDsJ50F2SidRlPbwJQcb4M1aXWvrlbwShCYa/Jg+AOB6xVSb+xdMrCusYKtzCxL9cobc6/maq5fu9UYoRJwKgeQAtLiJhHAFoM4SRL5FHmrD9VnmuxYKocsH7DCpOafTdCdRq23jjsCM8CXMygQUN3k/E5DJKn+9QAn/DeJQlBfGEr0HwpSwrgXnKoUfrsk0nfLlZhOlXZb1VG3hmQIClGcyt7znbS7rswWJGJKX40ky+hl1ZMnVhq1+CjjcpOZ5SUf0bS3I3YjEpnASbhJGRYv45sAgI8ZrUX8D7lQVq0aalyKPlqdYg/OezfItOgqY5n/Uj2/7eBwuTV+VflHu2TcaWUH7H7jBsNU4XZ5q3TrYLRb1O12YA3K3W4jp49gjBKr8zlScAReJZ5jg+3CfW3ODNIMbP67776n4XyxFdh7VaxkfNSQVWjHgbRaDmwV6S4xN0KnbGj0QybfnI2ZOD8tPVFGMUc8JKxyJsD/gJSIgkWdEJpG4Ho=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.6.0
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Added

  • Scenario / info text before a question (Codeberg #10). Every question has a new optional field "Scenario / info" for the case, situation or background a learner should read before answering. It is shown above the question in training, exam and exam review, Leitner, the smart queue, the daily challenge, duel, gameshow and adventure skill checks, and in the question list of the pool. It is plain text with line breaks kept, up to 10 000 characters, and it can be translated together with the question. Because questions are shuffled in every mode, the scenario belongs to the question it introduces: for a case with several questions, give each of them the same scenario.
  • JSON import reads the scenario from scenario (also szenario, context or info), JSON export writes it, and occ learning:import-pool-json accepts scenario per question. A scenario longer than 10 000 characters is never cut off: the import skips that question and names it in the error list. CSV import and export keep their fixed columns.

Fixed

  • An existing question with two answers could not be saved. The edit form adds empty answer slots up to four, and all of them were mandatory, so the browser refused to save until they were filled or removed. Only the first two answers are mandatory now; empty slots are dropped on save as before.
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-----
SignatureQqfTt1TeDKTUved8X8Dcv5vLVqimMtZy6VYots+lshbLgmtW4pBxjKtv/Xj2FMn8rmJnIXe1wvl7S4L7kwFraAHMrFxFBhaPG0x25cRWeL/61ukwAxscqgGBp1BpViWBcXsEcS/+xWZXSRklusg0BrCdWQAMu9xmMgeXwEs1L5boFkhqQ9WPOQ+MZj4qs+1iRBwuS6eK+O46Al5kmngQtjQT/1cq59s772yP+8sX2kR1AVhViLaxqIt3dPntDE27CfaKxCBH4jOrkPqJaDmv4WH6VqAGCXM0cClJBgQKwVaOHe9FYJk2ae06zaHoXLggLWvhUAr66m4DjpcUsozd+61wZX+nhXpvgLKvgMxMwugzmw9Ju2WqV3o6qbCOaYuOBXO4kDa3E1zUKYlLav6llzfTL3jccwXwaf7D2Yb+dJsaPO30/2zFUQCE5ws+gRw0ybDDIF/uEFtC35L9VE/9ENDqhUUq5VAZ8xdJ85e3wIIhSmIz6x1e5M+8C0xeUiX3mlX1BJaWHaFSehP8h6ci1Jn52pIgcpMXWfCMOuipRTnHzBTZcIdWJ+u/zcjDF9wf5Rg/HgOCSNDtDv7wq/WK45so5TanGuhPQs7sN3V2ruECYc1O6Hu3x6PqsHEka2++1rG0jjV4Lc48V91B++OJkAKYpq9DB2/jw0HlU6aHq80=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.3
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Fixed

  • Passing a course and issuing certificates failed on fresh installs. Every compliance event is chained to the one before it, and the chain starts from a first entry that the database update was supposed to create. Nextcloud skips that part of an update when an app is installed for the first time, so on every fresh install since 5.2 the chain had no start: "course passed", issuing or revoking a certificate and completing a mandatory training ended with a server error. The first entry is now created on install and on update. Where the chain already holds entries but its start record is missing, nothing is created automatically, because a new start would hide the gap; the admin log says so instead.
  • Question translations did not work on fresh installs. The translation tables were renamed in 3.5, but the rename also ran only on updates, so fresh installs kept the old names and every translated question failed to load. The tables are now created where they are missing.

Instances that were updated from an older version, rather than installed fresh, were not affected.

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-----
Signatureo8yBMrNyZAgnKOXnfigFnYCVUX3xjMbPbI1CLqQBcIOteYVfDOdQky/G0+BtQ4u3P5UD0Fe6bBhqfJQgrzsv6fOUxDg8lUQ5XbeC3GIftD6mgGi2JR3tZcxgLxEyXdCaMyzmHiT3FotUiCqbR7uWFDp5/UAUrpgxk/8zew7px3lrH9vrfXnGNOHDWib74vIH5Sjz1HFYXJpAyuhaFUuyYvREB8+r4ZGHjG5M/RZSaYk7uQ9r2+JvDUx+6BRCG8FOg+chwkmio7C7fMcHPeCgOxoaOlYo0or0ABpRNE/E8OUryh6z288ToP9ygqTX/m2VWWsIMGo1R21D4IYgSWy2T7g2LeWlrsg8NBbCOxJMR8KuQ4hO3DP7sgEz7Aq3RDHCG0lz7b31XV1ayfIPUERC1eO33G+7n9PF1fBR0HTYd0izOiOvYQENI4NuzwWNwZiiVaawMZbUZUwxKtSca6jrcBqnbv6iYwVKrgfKIb7BSAQ+2Bg1Dja7fy48c7eY0YGn/hX4jYWS7+HprjzBf8FCxruPha4ckCeiLNOtPxzTwuxBDOY+slYHEg6Bvggv0Dx3xpoAl0D5WBpNxZjM96dMIOGelrGNLB2WLWUg0q2QA4hUQIVlBOKRjEC4uMopC1+YS8z8pAWVFIj2mV6tICTQu8VXGd6i7NMsqUKm8XTEAc0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.2
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Security

  • The weekly study note was sent to the AI provider without consent. A background job runs once a week for everyone who studied in the last 60 days. It sent their weakest topic and the questions they got wrong to the configured AI provider to write a summary note, and it never checked whether the learner had agreed to AI processing. It now skips anyone without valid consent before reading their learning data, and the note generator refuses on its own so no other path can bypass it. This only affected instances with AI enabled.
  • Outdated consent counted as consent. The server accepted any stored consent version; only the VirtuProf chat window compared it with the current consent text. The server now requires the current version on every AI path, and the VirtuProf chat and the course summary narrative no longer use their own weaker check. The course summary narrative is also rate limited now.
  • A failed consent save was treated as consent. If saving the consent failed, VirtuProf closed the dialog and sent the waiting message anyway, although the server had no consent on record. The dialog now stays open with an error message and nothing is sent until the save succeeds.
  • Follow-up requests are checked too. Chat-memory compression and the pool generator send further requests after the first one; each now checks consent right before sending. If the server reports missing consent, VirtuProf asks again instead of showing a generic error.

A larger rework of AI consent follows in 5.6.0: consent will name the actual AI provider, the consent text will describe exactly what is sent, and everyone will be asked once to confirm the corrected text.

Fixed

  • The app icon stood out in the app menu. Learning used a coloured icon where Nextcloud expects the same monochrome glyph as every other app. The coloured logo is still used on certificates when the instance has no logo of its own.
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-----
SignatureDYtj4ULUbhWv/Own6XC4wfIshuUj8ND/dLsFst2krSvsBCs/c/lfxk81IpNAFtp5nCp7ihdBciuLw7XuQ7sfKUImWrXkE0sY2V38tD1XJWVFOpXIGBp0iDCVlxzUPFTWUuH+Ga9ItGIhUfIricBT70nkbcCN3FhTTLQqAYb89OsWHzV0DKiYLFTA3FD3ubeCahJBItQkfK0aGb7QZMKE8C3yGwAhPjFHDYXKyqiBlwbUVzjOTK7sZoWFkamh9ze9f7KwHZnvSEwUdnZmUyftTZMrJRycsPVA8cwuDNubH0Mbhw5/KMyeU7Hm65M2BJFAVm/8QPnaTSTf30RBNwi2es/DJ/S5IrRurWLVFvSXlEpAmHG82CDeN+sq6b+EF3NIjzBA8FNnfmbfG95rUYGImV9468yHSGkPkiyiMBqRj+efNImeUlhxeUdIXqMhVzB7DOFo4c3DzieKsKurmdcKCkoNCoEzh2Qybl5awSuSV9RUL5zpYVZ26FsH1hRLLu9yZOmDYHagkPFXw5aG6zazn3qSSRnikLI4kxrM3LtFXTUdlx3CxCcXczsWLkDL++nUMDxJP6p9LsbBrgple1tgGdfx5sMdKOIsJQiYF2gBJKrvcLjyji1M0PjJIJjOVpAuda/tdgcRY+1EYzkdcvxogW4Mh3Me45cLUK/EVYbIQ1k=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.1
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Fixed

  • Tool selections were saved but never applied. Reported again on #7 after 5.4.6: "regardless of the selection, all tools remain visible". 5.4.6 fixed the settings form, but nothing read the setting where tools actually appear. The course's learning space ignored the admin's global selection entirely, and an empty course selection ("every tool off") was read as "no selection" and showed all eight. Both levels now apply: the admin's selection, narrowed by the course's. With nothing left, the Tools tab disappears, and the dashboard drops its tools link when the admin has switched every tool off. After saving in the course administration the learning space updates without a reload. The unit tests for the learning space had carried their own copy of the old logic instead of calling the component, which is why they stayed green; they now test the component, and a rendering test counts the tools a learner sees. Verified in a browser on Nextcloud 33.0.9 with MariaDB 10.11, before and after.
  • The pool rules dialog showed every pool as required. The server sends the flag as 0/1, and the dialog read 0 as "required", so the checkbox was ticked for supplementary pools too — and saving any other rule in that dialog marked the pool required again. Found while adding the option below, which depends on that flag being right.

Added

  • Practice exams from required pools only. Follow-up on #9: a course may hold supplementary pools for self-study that should stay out of the exam simulation. The practice exam settings have a new option, Use required pools only, which draws questions only from pools marked Required in the pool rules. If the option is on but no pool is marked, instructors see a warning in the settings and learners get a clear message instead of "no questions". Tested on a fresh install and on upgrades from 5.5.0, on MariaDB 10.11 and 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-----
SignatureuJxsLjiKbsbrXM7v7CAn0FEqCEubH+3VTYxJgBTLm0Z4TxBY3sgcSV9+dDeV24fiHYL18f4tzB+013jku9suSZ67tZs+0cvzzhP77wy2LcRrV4VI6yFFOtmgS2HU+bc07Lrv/nIQz0HtcLfZjbenU1FyBRSUpWFGysQ8vuEabKS8m2Jneto113Qcty0YOWGqGoBI/MoQiaRlqM8XcF4kRodtPNmHxlGXUqJP/hO3tZP7WNIVBeDNz4p7TlpPEfjinuHniO2Gc6tmUK4b1B9BiCFzgXbxgV5ylzDgrbwbiqb2diqPezQFUaoIzcPshhR9602h1sL3BWi4McyvvDuEccXIxFLlvZYlH/O85yepBuzyKNsXquId/axgnKOQUJYWL0jPnat/yyUa+7MSRGVsYYro8xrVG4wGZD2sxXzDtolNd9F/gsg+4OR2lU5hUKOfBTuOp+cwQ/EXwX3wcyc7QszOvH8EDN0g24gC9dlB5Cs0ge6cybEx92RoWkE7bHf4CTe5FBhvSu+eRg1CTky1QS/x8NNsLbT4fDJvoID3QSwo+sj8Z9jUMN0gCT1qhu0iiXv3g0hQmnOcZhFwnmjoVNIw6HxNZlm+SZ4ceWjR4g/QZcPgmb7i0uVwR1gkBqJ3HOwc2fMh9VoEFqZ93SvRbTbB4t/eeSok7PEip8khvgY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.0
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Requested on #9 by a team preparing aviation radio courses: until now the exam mode assumed an IT certification.

Added

  • Practice exams per course. Requested on #9 for training outside IT certification: the exam mode only knew the fixed CompTIA presets (90 questions/90 minutes and 45/45, pass at 720/900). Instructors can now offer a practice exam in a course's Administration tab and set the number of questions, a time limit or none, and the pass mark in percent. Learners start it themselves from its own tab — no pool to pick first — and every attempt draws a fresh random set from all of the course's pools. Afterwards they get a review with the explanations. The CompTIA presets stay as they were; the practice exam tab shows even where the preset exam is switched off.

Practice exams never count towards a certificate: they are marked separately, and the pass evaluation reads only the other exams. For the same reason they are left out of the locks that keep an exam from being used as an answer key — an untimed one left open would otherwise block training and explanations on its pools indefinitely. The reverse direction is guarded: while a certificate-relevant exam runs in the course, a practice exam cannot start, and an old one withholds its review.

The pass verdict is decided on the server against the threshold stored when the attempt began, in integer arithmetic (74.6 % does not pass 75 %), so a later change by the instructor does not rewrite past attempts. Tested on fresh installs and on upgrades from 5.4.6, on MariaDB 10.11 and 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-----
SignatureQkyHXEoC5fAsNey6cnMRGCQpGB28CVQZ/54fNM1YfcAZEzfCZD8MoBlqlqq0ZhQli+CIHm+Te6xRdvA1lCrcnLODGjejcTgq/6+iXgcuSvJgd7Ev7IDm2ltRGUyq6j9DD3u85EfCvI01h37mIAWLYYsLBUP2C54qt9jU0rgyS0n0E1cp6r0+uGtd0ty/dHQwAycEmvlhAmyN9eOQXphbMK7sIItgBCnW0ykXEdxgKhCkpgcmMqxpSyrd1Ub4/lcLVoQ2JPxWun7/E9JVhPZ6XgpaRrrHlsAx0iQ/KBoKnT33jmGntcaz72vXBQZt5qWH3qkZoxinfpJvUDLE4VjvzMPyIHFqOBIZqA+HvCYMUnEpKAEuLy9AEwewLEnXqGYAH+CqpAojOtjaPHtWzd0oI8+CFEYt8VTfZS5ZD+ueMbYl/ARtq91MozvwjggM1UHfzeTybtnlJC77yYMxeqqQZtSupAS/GUboSIG3wu3PfD+x1X7PW5rJo+m3lR7vJ4Oz8SEIPNlt/s3jX+5lainSUAtOfYp30fCR0IcmkzmbajL+m7oBIqWQdSvxFqTUOr2+SmOmVERpXTikDC4X0XXe4cM1pbOiGolQqhc5RI2FC6c7aYcOT/rple7DLkKiT/I76LyqT7c/SE0NpJXv5N3DDAmuiAMti+PlreU3TiuTmSU=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.6
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Both reports came from the same install on Hetzner Storage Share: no shell, no occ, and MariaDB. #7 found a course view that failed and tool selectors that never showed their state; #8 found a false audit alarm and a certificate setup that required a command the host does not offer.

Added

  • Certificates can be activated from the admin page. Signed certificates need an issuer signing key, and until now the only way to create it was occ learning:cert:init-issuer — unreachable on managed hosting without shell access, where certificates could therefore never be enabled. Reported on #8 from Hetzner Storage Share. The certificates notice now has a Generate signing key button.

It only ever creates the first key: if one exists it refuses with 409, because an existing key may already have signed certificates. Rotation stays occ-only. Creation runs under an exclusive lock, so a double-click cannot leave two active keys — a guard the occ path now shares.

Fixed

  • "Checkpoint overdue" on installs with nothing to checkpoint. Reported on #8: the warning appeared next to "No checkpoint yet" and "Events since last checkpoint: 0". Overdue was measured from the last checkpoint, and a missing one counts as timestamp 0, so every fresh install was overdue from the start. The same false alarm followed every quiet week, because the weekly job deliberately skips when no new audit events exist. Overdue now means an audit event has waited more than 8 days for a checkpoint — which still catches a job that is not running.

The warning also pointed to occ learning:audit:verify, which checks the chain but creates no checkpoint. It now names the actual cause: a missing signing key (checkpoints are signed with it), or background jobs that are not running. - Tool selections appeared not to save. The selectors bound :model-value on native <input type="checkbox"> elements, which have no such prop — Vue rendered it as an inert attribute, so the boxes never showed the stored state. Every tool looked unchecked, clicking an already-enabled one changed nothing, and saving wrote all eight back. Reported on #7; four occurrences fixed. - Fresh installs never received three course tables. Version009900 does its work in postSchemaChange(), but a fresh install executes only changeSchema() and then records every migration as done — so it never ran and could never be revisited. Version010100 creates the tables in changeSchema() and copies any legacy rows afterwards. This is the likely reason a course view failed on the reporter's install, and why reinstalling the app reproduced the problem instead of fixing it. - The certification toggle always saved the previous value. It listened for @update:checked, which @nextcloud/vue 9 components no longer emit. - The active exam slot never displayed. The view read res.data.slot; the controller returns exam_slot. - Two buttons missed by 5.4.5. Both passed a styling value through a dynamic :type binding, which the new guard did not yet check. It now covers dynamic bindings too. - A missing database table no longer takes down an entire course view. Reported on #7 from an install on managed hosting, where oc_learning_course_curriculum_scopes did not exist. That table comes from a rename migration (Version009900) which deliberately aborts rather than creating an empty table over existing data — so an install can legitimately end up without it, and the app treated that as fatal. Curriculum scoping, paused-question overrides and announcements now fall back to "nothing configured" and log a warning naming the migration and the occ command that repairs it.

Writes are deliberately left throwing: reporting success for a save that stored nothing is the worse failure, and the caller has to be able to tell the user it failed.

The "is this table missing" check moved into Db\DbErrors rather than being copied — UninstallCommand has needed the same predicate since v5.3.0. It stays narrow on purpose: anything not positively identified as a missing table is rethrown, so a genuine database outage cannot turn into silently empty results.

This limits the damage. Why the table was absent is the "three course tables" entry above.

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-----
SignatureZ4KteEBRWfaEdZUSXUPV5e8sHFHO72aLZKRIFgUDMXjKJn97R3QodN3djq9hJLgcT0RUHxgZvfL4SeG2RvVYeCLOCIxcsGv6QV+yPoXw6w15Xay8CXBUExRuYRLKik+txLT0ttekRCHiTPrJU737R8CeodH0/dbItL6IJ/5JEOQv5iK5/8MLjL2wtJqBm2VeaE4H5nK/RYCvG/DNOTiFJgGgbhJlNhZcz+Yzg8LVyCCbRtQkU8MqagLTTAHbHCQU2+0h+0ommbO+Lcx6aHpvBKAic+VOB89YbbLoQpwtf5tirhjAM0FxT4EFK1OYTV1FfmRIz6wN1oIixcnPYSF6izmidK3jtTM+tBC6CqTUZ7lKtu+E+t7vWX+QrR2JEhVEi4FQ3bq/JoMUyGgnYxGvwB8p63T9SJeK0FEaI4fwgmHtA18xtjPUkUywE7CAp8DtVZ6Bgr163z1FUO8XOSsbPwUgh81VkIH/8NFHnReL5sBBDWBwFgsZ6iyMStv81OTZGglJBFyn9RjlV5HhrtprVLsDEr+G9BW9JOa/uM/dHFvuBxHGWdGadZ8QBlapI4ZD5Tz8GaWvPlvFYqeaovisViEcLN0TTSVN0+V95Cl2CZYb/4+fUKGPEhcTT5vEsb+mPYk/gCnz+NmqcSFp2ezR+GO2FWbx4EBSnbnlNFI8SEk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.5
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported in #7 by an external user on macOS, reproducible in both Safari and Chrome. The report was precise and its diagnosis correct — it just described one button out of 244.

Fixed

  • "Upload image" saved the question before a file was picked. The Vue 3 migration (380dd9a, 2026-04-03) moved the app from @nextcloud/vue 8 to 9, which swapped the meaning of two NcButton props: styling moved from type to variant, and type became the native <button type> attribute. native-type was dropped entirely. The v8 spelling was never migrated, so every button passed a styling value such as "secondary" to the native attribute — and HTML's invalid-value-default turns an unknown button type into submit. Inside a <form>, every one of those buttons submitted.
  • "Cancel" in the question editor saved the question instead of discarding it. Same cause (type="tertiary"), not in the report, arguably worse than the reported symptom.
  • The three save buttons only worked by accident. They carried type="primary" plus a native-type="submit" that v9 ignores; they submitted because the styling value was invalid, not because they asked to. They now say type="submit" explicitly.
  • All 244 NcButton instances were migrated to variant (the 222 outside forms were a visual-only defect: with variant never set, every button in the app had been rendering in the default secondary style for 5.5 months).

Added

  • tests/unit/NcButtonTypeProp.test.js — pins the v9 prop contract against a real NcButton render, and scans every .vue file so the v8 spelling cannot return. The four existing gates could not see this class of defect: it is valid Vue, valid JS and valid HTML.
  • tests/unit/QuestionFormSubmit.test.js — mounts the real form with real buttons and clicks them, asserting which ones reach a submit. Reverting the fix turns it red, on the submit count rather than on an attribute. Note the precondition the report already implied with "open an existing question for editing": while the required fields are empty, constraint validation suppresses submission on its own and hides the defect entirely.
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-----
SignatureX4k7LjqPFPx+SZ0KlaAr4FFFC9YdnYlt3mHWOnNskXRJI6+a7n2zgNlf6CwGnr09A8vJChj3uPg7y4MgnPZ4HLmtaNRPP05ol57+ev/o/2CZGElat0qGLwCHXE7/q71BVrcvzGXJOmc3B79R9iXjIFj+JR4qhWzZ1v0li3Fshz1mPwX2oQeAQ/7w5O1ffG5Vchk5hfJpU+X2phfuyBcxX3YpoUqNWOP6GAot8ENkl3znWoDidHICihBC1Szk25BWek0aUQng/ROo6zYn9xt8/d8xfIcJ0dBwvO7h29jeRDTxcRKFd4eBaXFnvesL58KZEC3vLSYduYzzK3ENccb8IXc0E8cqz0WXzI9V4l2cN5VaEYYpLO4I5++lwWqLPeHLAL6Gc543ihiDh04JgTuRA5VtrMGg6TCUpkmkWcdfKPEwuPWK+xq+cXBiHT9LyTqGsD6rfagyLGE2tnwnjni9++CIwj6ScsDU91CBFf/bZjDs21YouNXSpkkYsGFTuF4a3VTqFE1PuDcvZt02/1T+HX5Pwlj6BNm20O/81R2nuqtCU5kBgUbTLqtHimyufr0BKBWXclHZZEtKBOV/+iI6llWiSF6wDH/btNcgO9XlnGP3nK0S9x4GTTIXWqlsCZPILWByGYW47l6X7Fxb5fTgyZQMLz1kyV8om7Qs0q5+f34=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.4
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported in #6 by the administrator piloting Learning in Vinnytsia. Two of the three questions turned out to describe real defects rather than gaps in understanding.

Fixed

  • The assistant rendered German text inside a Ukrainian interface. VirtuProf does not translate through Nextcloud's t(); it resolves against its own bundled catalogue, and 161 of its 343 reachable strings had drifted out of that catalogue — including every FAQ answer body. translateVirtuProf() then fell back to the raw source string, which is German for most of them. 63 of the 161 were already translated in l10n/uk.json and simply never made it into the catalogue; the remaining 105 are new and now exist in all six languages.
  • The AI assistant was told to answer Ukrainian and French users in English. The system prompt's language map only knew German, English, Russian and Arabic, so anything else produced the instruction "If unsure, default to English". It now covers all six languages.
  • The assistant ignored the Nextcloud interface language. It derived the response language from the opt-in content-language setting alone, with a hard English fallback. It now falls back to the user's Nextcloud interface language, then the instance default.
  • Choosing Arabic as content language did nothing. The dropdown has offered it for months and the request sent it, but the server's whitelist did not include ar, so the value was silently replaced with the empty string and the request still answered 200.
  • "Create summary" wrote an English note for a Ukrainian user. NoteGeneratorService, which the assistant reaches through its file-intent path, carried its own copy of both the language lookup and the language-name map — and its copy was missing French and Ukrainian in exactly the same way. Both now come from one shared resolver.
  • The assistant panel header rendered three empty strings. Its translate helper passed the key where the function expects the language, so the panel label was blank and both the minimise and close buttons had an empty accessible name — in every language, German included.

Added

  • An instructor group setting in the administration page. Members of that Nextcloud group may create courses and question pools without Nextcloud administrator rights. The mechanism is not new — it has been learning-instructors all along — but nothing named it: the admin page referred to "the configured instructor group" without saying which, and the manual claimed the instructor role was per-course only, which is wrong for creating a course in the first place.
  • The five textbook fields in the pool dialog now explain themselves. They are optional and only pay off when a pool mirrors one chapter of a book; the dialog says so, and each field describes what belongs in it.
  • A fifth i18n gate (scripts/gen-virtuprof-strings.mjs --check). The VirtuProf catalogue is now generated from l10n/*.json rather than hand-maintained, and the gate fails when the two disagree or when a reachable assistant string exists in no language file. Verified: removing a VirtuProf key from all six language files leaves gates 1–4 green and turns this one red. The catalogue grew from 252 to 429 keys and lost none — keys reached through vt(<expression>), which no literal scan can resolve, are carried forward until they leave l10n/de.json. --list-indirect names the 22 call sites this protects.

Documentation

  • New manual section 2.3 on letting teachers create courses and pools, a corrected role description in section 1, a field-by-field table with a worked example for the pool dialog in 3.1, and two new troubleshooting entries.

Internal

  • deploy-prod.sh --stage-gate now copies appinfo/ into the analysis copy. Without it ClassResolutionTest — the regression test written after the AIController/AiController outage — errored out instead of running.
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-----
SignatureDLyhhVBf9XRwBNeoyTKog1TtpFna/YIjmDn+ZjlT9XdOHDkP+/lCeH5/llEg9T1YqKUzejWB6zRtksExb7/DCODs5W8r2AiSVkO1moPld7QVcjHOQXWZ7lXKqQ8FIDPKy6Xfa6QNbGc3HE1J0SGMfW315PV07TL074azidYLszcQzuVsoPcIir/FLptx8pdLReN2G//GucWdoGeAR1djjn4YfdFlCAE0eDuJnqi3dChkin0PMYFTbdK4uLqz6ocEkHcpjaw+0JbSe/7FZcoAkmEEHiV9rBbj9uikbBj/k1wYV8h4OD9TGJpK595NQ56GC4Py7FaCTD2mMLvjDiMsMYVq63hffv5xzwn0RPmAwosGImUpapd3ft5VTHuY+yP5prrrtNadF9Mm45KA0mPKrIJHOwey8xqyPfMl7N0RlhWBWwKbae436EQgTLifLdo4A/nz2gR/Fix58LLyQoHAKGKE/wdQ5jDrI5Dv7xA4cBzgcPeOC5S9vxUuAuKXuTQemliU9065EUw9nv+rTdTypx7CIzgVHtnjlXIapAQilENk1uLrg2b8AZD4PVXLq6UW1VLyO/H02XYKRuw67R4ZMbqg0tszrhamlCiGJB7WbvWbxP9NTfI0cFHZkq0dlRCUcUgIlQYj772fKw5t/cTHIJky8l9sBG/ObqWJVEawpC4=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.3
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Found by clicking through the 5.4.2 deployment: the wizard's "Skip" button sat at top: 16px, directly underneath the Nextcloud header, whose avatar button swallowed every click. The splash screen carries its own skip link at the bottom, so the first step looked fine — but from step two onwards there was no reachable way out of the introduction at all.

This is not new in 5.4.2; it has been there as long as the redesigned wizard. It is the sharper half of what #5 describes: users could not get rid of the introduction even when they tried.

Fixed

  • The wizard's skip button clears the Nextcloud header and can actually be clicked from every step. Verified in the browser: a click on the button's own centre used to be delivered to the header's avatar.

Added

  • Two browser tests that would have caught this: one walks a new account through the introduction and asserts it stays gone after clearing site data, the other asserts the skip button — not the header — receives a click aimed at it.
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-----
SignaturetBYdgRNTvcdREMCynplVM4OPCgWQck7GP+w3btJG710KKvOl1+YwKqBTcmUk3HoCwy+ws6BVOljRjCfJumhvCzPIIFpjs4fEsLskZyp7hueeRx9NTxCDe1fcWuZAA593HD1aDmrURuBWULQ09DRRcngYm3eD8X1yyr1oBvcwtFHkm+zMDOru484j4/Etl/R87I4vcA25ynoI1Rt8yxso52ZR3PFOb8Y+N4ghd7YlP01uffQAUlPEwQCrXwLXED7x6Z+ocSVcHVrmMFWx4f6ElksbV3UlIHd3ewB72t/kWNqCRqSTIf9L6DbyTb5MKFWwgBXRaP4vRbzZCrpukutZ5FprnR8XQ3KWNXfxJ4yW6nNYiZRYPuWu+sBKChWlDBa9RMgYnPv+ePV+H2EgW8NvI/DAXttcZDTsoFwZ0q/epmCuprdmcBBRefB3ysufGzzN7jQeVBEBr7dEkOtfadXpaLsLk03oQEu+1lM6Re7yPxYJukzWY3oG8tZXu92Q4TSnIAaSTAXP2+p/3wOEnm2u5r8dOnDQv3jUsSuJaGBBGRpIeTx1RAv2cWI698IAw3QJz1BQwnXpKdQei14hIRwacnB5mpJzj/N9cRmiGhQFrPaiNM2pcXoQGpLS73sa1p04Pl4bBrHFFqL3HzFqyKVGqhpH8kBMtR/NH1xcMebNYXY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.2
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported by the same administrator piloting Learning in Vinnytsia (#5): the help wizard reappeared for users who had already been through it, and its introductory questions ("How many bits are in one byte?") were beside the point for staff in occupational safety or HR.

The reappearance turned out to be three separate defects, not a missing setting.

Fixed

  • The assistant's introduction never honoured "no thanks". It read that setting from a response which does not contain it — /api/profile/telos/status returns the completion flag and nothing else — so the check was always false and the introduction started again on every single page load. The setting had been saved correctly the whole time.
  • Whether the wizard had been seen lived in browser storage alone. That is scoped to one browser on one machine, so a second computer, a different browser, a private window or cleared site data all brought it back. The server is authoritative now; browser storage remains only as an offline fallback.
  • Skipping the wizard recorded nothing on the server. It counts as a decision now, like completing it.
  • Instructors were shown two introductions in a row — the wizard, and the slide tour the moment it closed. One introduction, one decision.
  • Existing users are marked as done during the upgrade, so the update itself does not send a whole instance back through the introduction.

Changed

  • Instructors now see the full nine-slide tour inside the wizard. It used to follow the wizard as a second, separate introduction; folding it in keeps the content while removing the duplication. OnboardingIntro is unreachable for now and becomes the voluntary tour in 5.5.0.
  • Declining the assistant's introduction now puts the assistant in its reduced mode instead of leaving it in an undetermined state.

The introductory content itself is being reworked for 5.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-----
SignatureRWPqQbdfMQs4sWt1fQY2J12frNlvsBfyyxZVm5GD+QHIFuCDmqmhsunLgzv2zfzWn08hoinqIAE7hyOYf42Z38K17/vpiYxSnsltjK5f1gP1gomdeKwgGrTRpduOfDqXbH6KOGx82opBm1cjV6Whi2FVJrwCNE/MFwOzhdwshYHMWlOxVnCUDvm2iHfveakQ2fKbC7BAR3Sm8YtD/eUqsz/hNiGOZcCY71xo+LpUJge5bY18D9kz8GpXiwLhf8Ivzb5Dfj1b1xrwIS6WO19i1tgLpJ7adJXftyUv++o2DaPRLw1O3IcV0UUmmgo9GeDaq/1ZCtav5lu0r1O8crLQ1uSqZmwyzIkd/UG5feTsLC3ttF5/DA9nBhup7Gd6nIwluVKjaMUfyfmcypFS428rtlNefyJjgkD5gOi0aZH0GVE4cnEEkh3bzebPTLXHoTVjZ6SJIjYxQDHfRHJxObO4GCFuc/Fnq+MIGEySLlubt9q7Ja0GyphBmO1Vw3Z0fiCT24Ny6PrIpknjQcuUvb7/la6txUzB0e+IWGLm5YJf6FMZMgBSt4ZKOtUwONCrZ3RDHYIYLhtunfE1wCJNRt0ammhpTjUz99bjmQABkYfAeTQvznmZBzTi7PoEDyuHLm5sAPByjNhpySHDfe4nrLfArK30dmVoyqJgoBVve0+b79c=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.1
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported by the same administrator piloting Learning in Vinnytsia (#4): one error and one warning in the Nextcloud log. The warning was what it looked like. The error was the visible edge of a defect that had been silently discarding onboarding results since v4.2.0 (4 April 2026) — seventeen releases.

Fixed

  • The onboarding wizard could not save a profile, and lost two more things while failing. OnboardingRedesign.vue built its own request body instead of using the shared payload builder, and posted '' for the list fields where every other caller posts an array. TelosController::saveTelos() declared those parameters ?array, so PHP raised a TypeError inside Nextcloud's Dispatcher — before the controller body ran, which is why the controller's own try/catch never saw it and the user got a bare 500.

The damage was three times the reported symptom, because finalize() ran three steps inside one try with an empty catch: 1. the learning profile was never stored — onboarding_completed stayed false, so VirtuProf kept asking for a profile the user had already filled in; 2. the AI consent was never stored, even for users who explicitly opted in. Since the universal consent gate in v5.2.1, that locks every AI feature behind consent_required for them; 3. the selected starter pool was never imported — the wizard offers a pool, the user picks one, and nothing arrives.

None of it was logged. Three fixes, at three different levels: - OnboardingRedesign.vue now builds its payload with createTelosForm() / buildTelosPayload() — the same util PersonalSettings.vue and VirtuProf.vue use, so there is one payload shape in the app instead of two that can drift apart again. - finalize() settles each step independently and logs a failure to the console instead of swallowing it, so one failing step can never cancel the other two. - saveTelos() / updateTelos() accept array|string|null for the list fields and split a string on the same separators as the frontend (, ; newline). A malformed telos payload now answers 400 instead of raising an uncatchable dispatcher TypeError.

No migration is needed and no data was corrupted — nothing was written, rather than something wrong being written. Affected users recover on their own: the profile can be filled in under Settings → Personal or through VirtuProf, the consent dialog reappears when an AI feature is used, and a starter pool can be imported from the pool list.

  • Activity\Provider::parse() threw the deprecated \InvalidArgumentException. Nextcloud replaced that contract with \OCP\Activity\Exceptions\UnknownActivityException in NC 30; until then every activity-stream load logged a deprecation warning, and from NC 39 it will be logged as an error. Both throw sites migrated.

Notification\Notifier had already been moved to UnknownNotificationException — the same NC 30 cohort — and this provider was simply missed. Each throw site now has a test, so the pair cannot drift apart again.

Tests

  • tests/Unit/Activity/ProviderTest.php (new), tests/unit/onboardingTelosPayload.test.js (new, covering both the payload shape and the step isolation in finalize()), and seven cases added to tests/Unit/Controller/TelosControllerTest.php.
  • Every one of them was verified to fail against the pre-fix code before being kept — the controller cases reproduce the reporter's exact TypeError, and the finalize() case fails with expected [ 'telos' ] to deeply equal [ 'telos', 'consent', 'pool' ].
  • tests/Support/PhpUnitStubs.php gained OCP\Activity\IEvent, IProvider and UnknownActivityException; the PHPUnit bootstrap deliberately does not load Composer's autoloader, so OCP types live there.

Also fixed — found by auditing for the same bug class

Codeberg #4 was one instance of a pattern: a frontend that posts a body the controller does not read. Nextcloud's dispatcher does not reject unknown keys and fills the missing parameter with its default, so the request succeeds and the app quietly does the wrong thing. PHPStan cannot see across the HTTP boundary and service tests start after it, so nothing caught these. An adversarial audit of every such call site turned up four more.

  • Saving a course schedule deleted it. CourseController::setSchedule() takes entries; the curriculum tab sent schedule. $entries stayed at its [] default, and ScheduleService reads that as "replace the schedule with nothing" — it deleted every row, inserted none, and answered 200, which the UI reported as saved. An instructor editing a curriculum schedule lost it, with a success message.
  • Reported questions lost their identity. SupportTicketController::create() reads questionId/poolId/courseId out of context; VirtuProf's "report this question" sent them at the top level, so the ticket's structured columns were stored as NULL and the question was identifiable only through the subject line. The same component already built the correct shape for its other ticket call.
  • "Explain the question I just got wrong" did not load that question. The chat request sent questionId; the controller declares lastWrongQuestionId. The value was dropped, so the retrieval context never included it. The richer questionContext usually masked the defect.
  • Two icon URLs were relative where Nextcloud requires absolute — Activity\Provider::setIcon() and Dashboard\LearningWidget::getIconUrl() (whose interface says so explicitly). Desktop and mobile clients do not share the web root, so those icons did not render there. Notification\Notifier had already been doing this correctly.

Each of these is now pinned by a request-boundary test in tests/unit/requestPayloadContracts.test.js, verified to fail against the pre-fix components.

Also fixed — in the profile endpoint itself

  • An emptied help-topics list was silently preserved. saveTelos() forwarded the list only when it was non-empty, so [] and '' both meant "omit" rather than "clear". Emptying the textarea and saving kept the old value — and since help topics are published to classmates through the classbook at course or public visibility, a user could not withdraw a topic once shared. An omitted field (null) still means "leave as is"; a supplied empty one now clears, matching what updateTelos() already did.
  • A malformed updateTelos() payload could half-save. Skipping a non-array telos and carrying on meant {"telos": "oops", "bio": "changed"} stored the bio and answered 200 — a worse outcome than the TypeError it replaced. It is now rejected before any other field is touched.

Known limitations

  • List values are not round-trip safe: a topic containing a comma, semicolon or newline is split on reload, because the frontend has always joined and split on those characters and there is no escaping scheme. The string path added above follows the same convention rather than introducing a second one.
  • Users whose onboarding failed before this release keep a local "onboarding seen" flag while the server has no profile. They can fill the profile at any time, and the assistant prompts students who have it enabled, but there is no automatic reconciliation for instructors or for users with the assistant switched off.
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-----
SignatureD3aST42eiKf/nYaI5/U8YFgvDZ7RMjOBDOb+wZpO4QpWxrslmpBK86vnPCTj2glzExY08m104traCr2c59xfe/MYRWc3xfVgeBgyLQa0/KTJUO2ecJzP0pwMb24ybNYB5vSKq7ONgZPvv/9WNoo+BKPzos3CT2xpO0+0P0oKGXyErMCU2kJnG7kfTr1ih2jzurKy3vEXGsj43Hth/skA+DNRToPZH6fMyjBYkhaK0ezVO4+Sd18jImh7kAdEl1uX429jC70cQCrurkT8+4KPax0QCrOSza7Va7qHAnM7NuqHl+Yn0lNQwvUSTlnP3dpkUVOHg/e2REyROD76NiPcv7RX1vQH1Yli8eQ6MOfg2wM7gq9sCcJ9yG83VJ9wR+CZqDHbBYe7hm09p70J1Hrx3p1vDmYVhl8CapA1H1tZz58YkoTwbycwbesy6xbtM+nG7m20AjE09s9Ye3Sg+vWWu7bQCbKEZCt4OXxEHwQWXX6VQ0toFNDm8ZIxFhnyMG4C7Z/kIC51h5wASBCE+ZNyTmhDZogBEfbJrdxu+Zq8JIPq++s1IBUUHUFUvBbwP5iydFF2Ftcu4EzbxvwfpqKJNiAMbCJFOUMPYFAXL4ToSLPzqZDtVZpzki9b44wtyJ0io74iu4U1ohgD/8erbwz21qXnzasdTwxtyRPNfBejhLo=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.0
Release Details
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 a.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
UpdatedOct. 15, 2026, 8:25 a.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_announcements → learning_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
UpdatedOct. 15, 2026, 8:25 a.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
UpdatedOct. 15, 2026, 8:25 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.10.0
Release Details
UpdatedNov. 13, 2026, 9:25 p.m.
Changelog

Reported by alphalib-g in GitHub #35: a small organisation that certifies its people every year found that learners passed and never got a certificate, that learners enrolled by typed user name could not see the course, and that learners could not open the course materials.

Administrators, please read: certification follows a new rule. - The certificate is issued right after a counting exam. Before, it was only issued when the learner opened the completion tab, and that tab was hidden unless the course summary was released. - Every required pool needs its own passed exam. With required pools, each of them needs an exam started in this course's exam mode at or above the course's Minimum score (%); the best attempt per pool counts. Without required pools, the best exam of the course decides. Leitner mastery no longer counts towards the certificate. - Exams taken before the update count under the new rule, including the former 45-question "Practice exam" card, which always counted as an exam. A learner who already passed but lacked Leitner mastery receives the certificate the next time they open the course's completion tab. - Practice exams and the new pool practice runs never count. - Instructors of a course get no certificate in it (owner and co-instructors), also not through the student view. - A recertification counts only the exams of the new period. The period opens once the certificate has expired and the grace days (default 14) have passed. Before, last year's exams renewed the certificate without a new exam. - Every PDF and Markdown file directly in a linked material folder can now be opened by all course members. Link a folder that holds only course material; the home folder is refused. - Exam slots are removed (Administration → Exam Slot). The setting said learners could only take the exam during a slot, but nothing on the learner side ever checked it. - A repair step corrects member ids. Course members and pool shares stored with the wrong letter case (for example "Alice" for the account "alice") are rewritten to the real user id. A row that would then duplicate an existing one, or that names the course or pool owner, is deleted instead. Rows of deleted accounts are left alone.

Certification

  • Issued right after the exam. The exam result screen judges a counting exam by the course pass mark and says "Certificate issued" with a button to open it, "Pool passed — still required: …", that the pool is not required for the certificate, or the pass mark and the retake rules. If the certificate check fails, the result is still saved and the screen points to the completion tab.
  • Pass rule per required pool (see the note above). Only required pools that are still attached to the course count; if none is left, any counting exam of the course decides.
  • The completion tab ("Completion & certificate") is always shown in a certifying course. The course summary switch now only releases the statistics. The tab shows the pass and certificate state (issued, expired, revoked or issuance failed), the required pools with the best score, the pass mark, the validity, which exams count, and the retake rules, and offers a retry instead of a blank tab when loading fails.
  • A failed issuance is reported, not shown as success. Without a usable signing key the learner reads "contact your administrator". Under Participants → Completion & certificate, instructors see who passed without receiving a certificate and whether certificates can be signed at all.
  • Recertification counts only exams of its own period (see the note above). The same applied after revoking a certificate: exams from before the revocation reissued it at once.
  • Instructors are never certified in their own course. The rule is still checked and shown, but nothing is issued or written to the audit trail; the completion tab and the result screen say why.
  • An expired certificate is named on the result screen, with the date and the note that a retake counts once the renewal period has opened after the grace period.
  • The certificate settings warn when the course's Exam mode is off, because then nobody can be certified.
  • The certificate notification opens the course's completion page.

Enrollment

  • Enrol people and groups by search. Under Participants, search for people or groups (Nextcloud's sharing rules apply, at most 25 results), choose the role and add several at once. Choosing a group enrols everyone who is in it right now; people who join the group later are not added. The result names who was added, who was already in the course, who was not found and which group was not available. Existing members keep their role, and a different role picked for them is reported as not changed. One step enrols at most 200 people, 20 groups and 1,000 accounts in total.
  • Members are stored under their real user id. Typing "Alice" for the account "alice" stored "Alice", and on PostgreSQL that learner never saw the course. Pool shares had the same problem. Existing rows are corrected by the repair step (see the note above).
  • Clear messages instead of "Failed to add member": unknown account, already a member, not an instructor of this course.
  • Group privacy: when sharing is limited to own groups or user enumeration is restricted, a non-administrator can only enrol groups they belong to, and a typed account outside their groups looks like a missing one.
  • Enrolled people get a notification that links to the course (not when you enrol yourself).
  • The course form's group field works. "Enrol all current members of this group" enrols the group's members when the group is set or changed. The group is checked before saving (it must exist, be allowed for you and have at most 1,000 members); otherwise the course is not saved. Saving the course again enrols nobody, so people removed by hand stay removed. Before, the field did nothing.

Exams

  • Exam cards follow the pool and the course. The exam has up to 90 questions from the pool (all of them if it has fewer), 1.5 minutes per question and at most 120 minutes; it is disabled on an empty pool. Before, every course offered fixed CompTIA cards (90 questions, 90 minutes, 720 of 900) and a 45-question "Practice exam" that in fact counted.
  • The CompTIA 720/900 scale is only used in courses without certification. In a certifying course the result is a percentage against the course's Minimum score (%), the same rule as the certificate.
  • New "Practice run" card per pool, shown only when the course offers practice exams. It uses the course's practice settings (questions, time limit, pass mark, attempt limit, cooldown), ends with a review with explanations and never counts towards a certificate.
  • An exam started from a course is composed by the server: at least 45 questions (or all eligible ones if the pool has fewer), without a question-type filter. Before, a shortened request could turn a single correct answer into a pass.
  • The exam setup names the pass mark when the exam counts; texts no longer promise a simulation of the real CompTIA exam in every course.
  • Exam slots are removed (see the note above). The exam date only drives the learners' countdown and reminders, and its help text now says so.

Course administration

  • Learners see the course schedule as a timeline on their dashboard. Before, they got "no schedule" because the schedule was readable by instructors only. The timeline matches entries to pools by the pool's chapter key or name and shows a load error with a retry.
  • Schedule editor: a chapter ID column with suggestions from the pools' chapter keys; a repeated ID is named before saving, and a refused save shows the server's reason.
  • Every learning mode switch says what it does. The Leitner sprint setting only shows while Leitner is on, the adventure campaigns only while Adventure is on. A note explains that gamification is a server-wide setting.
  • New switch "Show simulator tools in this course" (on by default, so existing courses keep their tools).
  • Saving the tools, the sprint setting or the Talk room no longer stores a mode switch that was changed elsewhere without its own save.
  • The student view shows the course the way learners see it, without the instructor tabs. An instructor enrolled as a learner in a colleague's course no longer gets the Pools, Topics and Knowledge tabs there.
  • Help texts for maintenance mode, topics and chapter keys describe what the app actually does.

Materials

  • Learners can open the material files: file names are links. Only files directly in the course's current material folder are served, and files of a view-only share are not.
  • The extracted text is no longer sent to learners. The document list used to hand every course member the full extracted text of each PDF and the extraction errors; status, errors and paths now go only to the course's instructors.
  • The owner picks the material folder with a Nextcloud folder picker; the page says that course members can open every PDF and Markdown file in it, and the home folder is refused. Re-picking a folder that was typed in no longer registers its files twice.
  • Extraction is explained: with AI off, extraction status and buttons, the Knowledge tab and the learners' Swarm tab are hidden; with AI on, the page says that extraction only feeds VirtuProf. Without pdftotext the page and the admin settings say so, and "Extract all" leaves PDFs alone instead of failing them.
  • Learners get their own empty state, and "Learning materials" comes first in the learning space when a folder is set.

Fixes

  • Database errors when starting a training or exam were sent to the browser with the raw driver message (table and column names, SQL); they are now logged and answered with a fixed text.
  • A running exam resumes only in the course it was started in. Before, an exam started outside a course or in another course sharing the pool was resumed here and counted for the wrong course or none. Now the learner is told to finish it where it was started.
  • Practice attempts are no exam scores: they no longer appear as a pool's exam score in the learning profile and earn no exam badges (quick thinker, exam ready).
  • A schedule that failed to load can no longer be overwritten from an empty editor.
  • Course materials that failed to load show an error with a retry instead of "no learning materials", and no folder, scan or extract controls.
  • The dashboard exam countdown skipped every exam date saved with a time.
  • Translations: the exam-mode hint was English in French, Russian and Arabic; French showed "Group" instead of "Groupe".
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-----
Signaturep1mTaRy9uh56bfMkWJoABvbRoR3wPat7BYtozHJHNhlitCAJTgX4d3t20IvYgB4jU5mbrlB2qAaQBlRjwDlyF7XveAJBqlrATVnocWVrX4B4In4pbot5i0RFpuw/i+TAdkUas6EtLIX9QfPH/kxBYMaxuZgoEjv2T4XwzP04z5fE/M1uTwsBwwMXfyPy8aLj8JT5Ca6HFIZSMyyylBqrSvMt2LIm7H/+BeMYYWSBckSkgNLaas5uDdxAIA1HauIS887udrCscVyYt/+GZwgmJ7oumkzsm1CPGndrykp6X6p3nXkrDp23NXP337ipW9pi+2w9GKwrhiy35TAcTj5+k6RWU2nKok9FFmjHVJ+4fwHvLOyWfX549A2Or3gmEBJmhV3E2EQY199M3EhNzyn5zJb6mKUcpXJxeukjWB64n1nlSh9JVlD6DOufpI5XZUyIlTzu0xl6uFsYFaFaXEIudw9S/DsM9lHRkl23To3u54EM7J+oYPwU3gdxqXk08GWdtlsuz3onenx9/cs2OTohepCl8JsB8ss5jUiYxAOHTBp6kLYCDfQM+8lXnVCbJb4XgwUSlzqkDlnZy+XLSOd5CBgNp2ikWlRSV/pH9vw4/wWfjYjWbIK2yQMqKY3yFTZBLAoL411eM6AMM1QVs5iugwtl4N+8WTDVMSFkNJaFXTA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.9.0
Release Details
UpdatedNov. 12, 2026, 9:33 p.m.
Changelog

Administrators, please act after the update: enter your operator details. The imprint and privacy pages no longer name a fixed operator. Enter name, postal address and contact email under Administration settings → Learning → Operator details (imprint and privacy notice). Until you do, both pages show a neutral note instead of an operator, and Learning no longer sets the Nextcloud imprint and privacy links (Theming). Links that already point to Learning's pages are kept and lead to that note until the details are entered.

Fixes from a full code audit of the app.

Security

  • Learners no longer receive the answer key with the questions. Correct-answer flags, explanations and interactive (PBQ) solutions in question lists, search results and translations now go only to the pool's owner, users it was shared with as Can edit, and the course's instructors. Learners and read-only shares no longer get them. Practice modes (training, spaced repetition, bot games) still show the correct answer after each answer, as before.
  • Exams no longer leak solutions through side paths. During a graded exam the question APIs withhold the solution of every PBQ type, including the grading data of command-line and multi-panel PBQs, and the cheat sheet leaves out pools with an open graded exam.
  • Adventure skill checks only use questions the player may see. They draw only from published pools the player has access to, accept only the questions they offered, and neither draw from nor reveal answers for a pool with a running graded exam.
  • Duel and Gameshow accept only answers to the current question, and the Gameshow sprint time bonus is measured on the server clock and capped at 500 points.
  • Course archiving and reading archive snapshots are limited to the course's instructors and Nextcloud administrators.
  • CSV exports of at-risk learners and the class summary neutralise cell contents that a spreadsheet program would run as a formula.

Changed

  • Operator details are an admin setting (see the note above). The imprint and privacy pages use only what the administrator entered.
  • Bot mode in Duel and Gameshow grades your answer on the server, because the browser no longer knows the correct answer.
  • Exams: answers that arrive with the automatic submission up to 30 seconds after the time limit are still accepted, and the exam submits itself 5 seconds before the end. Before, all answers of a timed-out exam could be lost (0 %).
  • Gameshow: when the 15 seconds run out, a missing answer counts as wrong and the game moves on for everyone. Before, Sprint and Elimination could stop on a question nobody answered.
  • Required Vimeo and YouTube videos report their progress. After the learner's consent the page loads the provider's player script, which tells Learning which parts were watched, so such a video can be completed and unlocks the course quiz. Before, it could never be completed. Nothing is loaded from the provider before consent; the privacy page lists the new category.
  • The nightly XP consistency check never lowers XP or the level. It used to reset XP from single training questions, streak bonuses, Leitner reviews, missions, goals and Gameshows overnight. XP already lost this way cannot be restored.

Fixed

  • Team lead dashboard: members with a valid certificate (without expiry or expiring in more than 30 days) are no longer listed as missing; expired certificates show as expired, with a reminder button; the newest certificate counts. Due and expiry dates show as dates.
  • Exam mode: Enter on a focused button or navigation dot activates it instead of skipping the question, and keeps its hotkey meaning after a mouse click; the next PBQ in an exam starts with its own empty answer; pool editors keep the solutions and their explanation translations during their own exam.
  • Required videos from Nextcloud Files send only the newly watched seconds, so they can reach completion.
  • Unified search works on PostgreSQL.
  • Course material search (RAG) keeps Cyrillic, Arabic and accented keywords and keeps the text directly under a Markdown heading; a sub-heading directly under a heading names the chunk.
  • Imports: pool_name is read as the pool's name; question type multiple_choice becomes single or multiple choice by the number of correct answers; the occ dry run counts only what the import would store.
  • Adventure mode: skill-check questions work on MySQL/MariaDB; a new skill check starts with an empty answer batch; bot-correction scenes no longer list the correct option first.
  • Games and leagues: a duel forfeit goes to the player who stayed; co-op players who were ready first leave the lobby when the game starts; ranking PBQs are scored in the spaced-repetition queue.
  • Leitner: switching the language during a review keeps the queue and the current card.
  • Course feed: "Load more" loads the next entries.
  • Exam reminders find exam dates saved with a time.
  • Maintenance mode can be switched by co-instructors.
  • Material scan detects duplicates per course instead of across all courses.
  • PBQ builder: applying an existing PBQ keeps the fields the builder does not edit.
  • Navigation: a pool opened from a course keeps its course context; the app works when Nextcloud runs in a subdirectory or with index.php in its URLs.
  • Calm display: the switch shows ON when animations are off, and the setting applies after every page load.
  • Learning profile: the history trend reads sessions newest first.
  • Onboarding: "Skip" on the Jumpstart screen declines the selected starter pool.
  • VirtuProf: auto-read and chat scrolling react to new messages.
  • AI errors of the note generator and the weekly plan are reported by cause (not configured, rate limit, temporarily unavailable) instead of a generic server error.
  • Admin settings: an exam cooldown of 0 minutes is shown and saved as 0.
  • Tools: the subnet trainer accepts IPv6 answers in any valid notation; the VLAN frame preview puts the 802.1Q tag after the source MAC.
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-----
Signatured0EiRXMmplgSSmtW8lYHyMhlK+d+z3FllLYfd6W5/KOI+d4zZ7TEvr3npFdou+eaiRCL+8VWyqTY5yjMt04R5txLiL37+A5ENjxDu1g5AmDsJjOqZiKI+KY5YqFP6tZUGOvd2t4stZVkdt8EFI6Wt7vyhLeMXiCTFU3aj6m/SXhf2E9TDiQT3n5fJI9CQcz1V7RHuWCQHQfrjBLbobxhDK5OiqKYdl0adhUJv/Ob2QZAm9sreFAxiN7o5ugYJ3UNePLmXF4aPqzvcAuWKwCmCRnh0EOBpx1VSucwj5eI0h4epBmSyZoitun3IBSMw4xhxFOdMgvhMejcfbEp+y8VXEGkIxZkCCHg3Xrcj+R4T1KX2YasObMlJhNhvA40gr+DLSK18y3UZvHMiOtwWKHzuhzESBJ0u8ZCRPaQr2kkg6yqwHRK7ZSmHJQioLnim6/F40xwUk6qmo4c5y8tiwtJfE9nCRsxwlwZ5evDa8i5d+LfNQvifILYt8OQTpBfee8qSeIKendI+wgOTVZ/mg30I3RIQV0GMjI+dThfzpK5/IfJooifQnQKu9H/LjXovMUUhNIY1wl8tVaO9MNMlGBpiZ/wb2AfrsYAPHk3vLnrLEwa8KDC8JZqNJZucqdpaMuegSrGE+yi525CQBQiCl1bokb4DVx9eQjt31D7JVS7aFU=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.8.0
Release Details
UpdatedNov. 12, 2026, 3:34 p.m.
Changelog

Reported on GitHub #34 by a self-hosting administrator who could not find where questions are added and whose JSON file did not import.

Fixed

  • Pasted JSON was read as CSV. Pasted text that is valid JSON now switches the import dialog to JSON by itself; broken JSON stays on the CSV tab with a note that quotes the JSON error. Uploaded files are read by their extension (.json/.csv) first, so a .csv starting with [Network] … no longer goes to the JSON reader.
  • Many common JSON shapes were refused or crashed the import. The import now also reads answers as plain strings (options, choices) with the correct one named on the question (answer, correct, correct_index counting from 0, correct_number counting from 1, a letter or the answer text), the documented pool file with one pool, and numbers where text was expected. A marker that names no answer skips the question with the reason in the import report.
  • The pool format lost its metadata in the interface. The pool's chapter fields, exam_key_prefix and the _meta defaults now apply to the questions in the order question > pool > _meta, the same rule as occ learning:import-pool-json.
  • CSV with line breaks inside quoted fields (as spreadsheet programs write it) was torn apart. Quoted fields may now contain line breaks, the separator and "".
  • An export did not come back complete. The JSON export now carries difficulty, chapter, handbook and exam key, and the subtype and configuration of interactive questions (PBQ), so it re-imports without losses through the interface or occ; ranking PBQs can now be imported. An exported multi question with one correct answer stays multiple choice. The CSV export (also the course statistics export) is plain RFC 4180 now, the format the import reads, so a backslash before a quote no longer breaks the re-import.
  • An empty pool was a dead end. It now opens in Manage for everyone who may edit it, and Training offers Add questions.
  • Wrong answers could be marked correct. A correct answer after the eighth was cut off before it was looked up, so the first answer was marked instead; a correct-answer list naming the same answer twice (such as [1, 0, 1, 0] or ["B", 1]) was silently merged. Such questions are now skipped with the reason, as are questions with more than 50 answers and correct-answer lists longer than the question's answers.
  • An unknown question type failed the whole pool. In occ learning:import-pool-json and the pool import, the dry run counted a type such as multiple_choice_extended as importable, then the real import refused the entire pool, valid questions included. Only single, multi, open and pbq are accepted now (any letter case; true_false reads as single, the historical multiple as multi). Any other type skips that one question with the reason, in the dry run, the import and the question import of the interface. Creating or editing a question refuses an unknown type; a question that already carries one can still be saved unchanged.
  • Out-of-range values. A difficulty longer than 20 characters failed the whole import with a server error; it is now cut to 20 characters. A chapter_order outside 1–9999 is dropped, in the interface and in occ alike. Answer texts over 2,000 characters are still cut, but the import report now warns. A CSV header row no longer counts towards the 500-question limit.
  • _meta.default_question_type: "pbq" no longer turns a question with several correct answers into an interactive question. Interactive questions accept text, question and frage as the question text, not only question_text.
  • Import errors from the server are shown in the reader's language.

Changed

  • The Manage tab follows the pool's edit rights. The pool's owner and users it was shared with as Can edit see it, also without the instructor role. Creating a pool still needs the instructor role.
  • The student view is visible. While it is on, a banner on every page says so and offers Leave student view; a role that could not be loaded is reported instead of silently showing the learner view.
  • The import shows a preview, for JSON and CSV, of how many questions can be imported, and why the others cannot, before anything is saved.
  • occ learning:import-pool-json reads an export directly: a plain question list, or {"name", "description", "_meta", "questions"}, becomes one new pool, named by the new option --pool-name, else by the file's name, else by the file name. The command now also imports PBQs, and a question's own chapter wins over the pool's.
  • Manual and import guide describe who sees Manage, all accepted import formats and the round trip export → import.

Added

  • Import pools in the Pools tab. Instructors can import a JSON file with one or more pools (the format of occ learning:import-pool-json, also a plain question list or an export, which becomes one pool named after the file) in one step, each pool owned by them. A preview from a server-side dry run lists every pool with how many questions can be imported and why the others cannot, before anything is saved. Each pool is saved on its own: a refused question is skipped, a pool that cannot be saved is rolled back completely, and the other pools stay. Up to 50 pools and 5,000 questions per file, with the same file-size setting and rate limit as the question import. New endpoint POST /api/pools/import (instructors only, dry_run for the check run).
  • occ learning:import-pool-json and the new import share one implementation, so a file gives the same pools either way. The occ dry run now checks every question and lists the ones that would be skipped, and a pool that cannot be saved is rolled back as a whole, with the technical reason written to the Nextcloud log instead of the output.
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-----
SignatureVrC4hI1zjAahOe8f5+nyNpqhRuV8zH8by5r8MhjccizM5rWKNF4I4J2ENiI4sh/Ja12panTqJuyWZc837YRnxukcjbMjLj1KxLoX0ZYXas8a37pkoJ8sIkXmmby8mGXsFjuQsvuxN2cvkMU/uU07gxxg3myFbQge3b+ND92bHCO9tl7IGyyCVTNIwtdQAL4ldaS6fONrUxlXMOA0c+FEEWGqhAHBNMwUX7ChacWOM446YR4jZTzEKckL5BBTqPjtg94W9eX8/vapA5ZRVvbfoAtKXWkMULpGGHcEvwwds+hBZ2bxvOuyqcJdCEgRVWD+Xp/aBVhDTqiq+4+d738sO8ILhFxchpcOIADHbcPyfO2RldGQZMxA8pD9CJu1dKHB+9mOg3oar9Dx4wF5dZGjYqew+/jo12pXSdSDc5PRaQxvbcYSzPQZwkl1118Tw9yFs441sFhCES7SQS7/fnM3bx1JECwXMA3O9oJq+ysQRsl2Nk1mI3+UvtQAXr5RgD26x3/F1HOWt99g92jEcOpPFFU1ZvfYYHO2G5NM9z1akY1ibgqCHsxETZjamI1KulFEWmhcJ9iYN9TXTP7WuO6vnqaj50ZjXWvXtwGfn9yFB/jjyYrQw6tdRIhSeZyqGtQeJdU5qMWdyZ2hl6RSl6n+7UiCP3Ie7bIuEUTBjcwKDRI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.7.1
Release Details
UpdatedNov. 4, 2026, 8:18 a.m.
Changelog

Fixed

  • "DevCloud" no longer appears in the interface. The name of the author's own server was hard-coded in the onboarding ("Welcome to DevCloud!"), the imprint and privacy pages, the personal settings and the VirtuProf privacy hint, in all six languages. The texts are neutral now ("this server", "the learning platform"); the PWA guide no longer names a fixed server address.
  • The streak and user state failed on SQLite (/api/v1/user/state answered 500): the query used MySQL's FROM_UNIXTIME. SQLite now uses its own date function. PostgreSQL and MariaDB were not affected.
  • Audit checkpoint anchoring had a default host. Without a configured Forgejo URL it fell back to the author's server. It now skips the anchor instead. Anchoring stays off by default.

Changed

  • Notifications (toasts) come from @nextcloud/dialogs 8: accessible, and shown at the bottom start of the page.
  • Development tooling: PHPStan 2, PHPUnit 13 (tests only; the app still runs on PHP 8.1+).
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-----
SignatureHV8IxQAN0P9065NUlpA9bAX9eDV/BplS4KgVDTjS7map+w8Z/de0MYkfQ7uM/ypq6yVCSkO4u/JxyZO50joNh6sxAnfjZq/JJepaAeycRr55qrwxBRM4+/ScP5tVs+RYcZl8ZU0+eKWS/mcevp77wzqoPEaxqOe6p7ysKZjUyCTUfnLp+B8pI4fyt13ji/DC5meFijLB23brN0SMhDEhHxUh8ScKk+PRpSgzMzyNIUQALBiV7aB+TIYLPl9pbVKkHaNvt8PRVYyPwJgypdw1FxgnNR+X4wFlGK2mTjsuKroPKjkyzYkIUHokxNiJasX8O1NfrDaZOgOigCsM38cTXB1Vg/Xw04znmM4AYGg7q8u4Pze/DvDWNI4r8mGQg7tbnCUiwLhhD9iQauIlFadqjLumX+8a4z+KVOZVqGHbgNNrjXeZJTonIvQcjvhFUWFTbtGReO84IxQUbYaVlVo4pjxP7QB6v6yRLpr/RufmqzfGZ9qolSc4AVlyoM1SlBNHvYMaWdCzaTeqNZMcQlB5q5LqRXMm0N7RtkxzK24BUJiegAJ+9UWybizbAiYKLHFXXnv09XjHhUTgFXSviU2EfD1zscQuBvgZDwXR7M8wr5lbyWsVPG8SYrJjA2MV8glpg5PbBHzCCBYE/+QNchr11ZSLBzI3kJxGJhG8NnK/9sM=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.7.0
Release Details
UpdatedNov. 4, 2026, 7:38 a.m.
Changelog

The AI features (VirtuProf, weekly study note, learning plan, course narrative, story narrator, pool generator) work as before, with Gemini or a local Ollama server. What changed is how consent works and how a request leaves the server. No new AI providers yet; they follow later.

Changed

  • Consent names the recipient. A learner's consent now covers the current consent text and the AI recipient the admin has configured (Gemini or a named Ollama server). If the admin switches the recipient or the consent text changes, each learner is asked once more before anything is sent; the dialog says what changed. After the upgrade every learner who used AI sees the dialog once, headed "revised".
  • One way out. Every AI request goes through a single gateway. Right before sending it checks again that AI is switched on, that the learner's consent still holds for this exact recipient and that the limits allow it. Consent revoked or recipient changed in the meantime: nothing is sent, also not by the weekly job that may already be running.
  • Real revocation. Learners can withdraw consent in their settings. It takes effect at once.
  • Limits per learner (10 requests a minute, 100 a day, 3 at the same time) and a daily total for the server, counted in the database so parallel requests cannot slip past them.

Added

  • Accessible consent dialog in all six languages, with keyboard focus kept inside the dialog and right-to-left layout for Arabic. It is the only place where consent is written.
  • Connection test for admins: sends one fixed test message to Gemini or Ollama, nothing from any learner.
  • Data export lists the AI consent and the stored chat summaries. The public privacy page names the configured recipient.

Security and privacy

  • The Gemini key is stored encrypted; the upgrade moves an existing plain-text key over.
  • Requests go only to the configured address: no redirects, no automatic retries, the address is resolved once and pinned. Link-local addresses (such as the cloud metadata address 169.254.169.254) are refused for every provider, also for an Ollama server in the local network.
  • Frontend dependencies updated for known vulnerabilities (Vue, axios, DOMPurify via @nextcloud/vue, PostCSS, nanoid, form-data, among others).
  • Prompts, answers, keys and provider error texts no longer reach nextcloud.log or the audit table. The upgrade removes such content from older audit rows.
  • Contributions of other learners are only put into a prompt when the recipient is a local server.
  • Account deletion also removes the learner's AI limit counters.

Upgrade

  • AI tasks left over from 5.5 are cleaned up; old task IDs report as expired. The upgrade stops with an error instead of continuing half done, and resumes after a crash.
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-----
SignaturehCSbN80e31JkExc32f+TDb7MdXg+K34/KfGKbWb92DBagD8pLGHsnhBwvFxTenCDEXBm2eobnLcTe6MEQaBSL+OIs41oVB1An+tdRLtnKjMVtMcXs9PA3PPQORljd+Vvh2ptodCa3Z+UTsvhiwZN4G1ESjzMVt2Ql0Po0FFzgmFVFqkPLVPGnbyzxrQZ2DNZWgukzq3DItHlRFa83i0KzXHIgKoJB9kKji9WrQWZjjIlHeCcK8ZgvAO9iYqDf+CrnkcdaWY80lny9lZj2J7oD3YOY/OBsU1BpcAl6E11jDPEHGwgHN/mGZ+WQMr4JqsXkEFxeqLDiC6zktHVCoKPAdpcOmqiUWIvAR5rfNIIA92p95rFbZWsaW2VwPahwKZryUcCHwcLIhxCKCw8M958lga0DoBxMQwsyo6n9j6DYnHJrtnfVZqlOMTv4kzJ1PrfR26o5HWN/k0TUI94H3AW5+1N9BqkIJ9zICVzQCm8oM9kXQwUJ5iafdmiKctoCCz8mwxVFUIHq0se9lpRW4bDEVSVQo1TmAIwPoRWwW1l5zgZpJM2XlBawyPNBDKILKrB7D+mTW8R6lh659E3qNEowkE88AaHIWBRipMdSh/ALxvBM5tneZC8RUOhIzaZITtLUSXB49C0AdPV4eoeuHzum5xmN8hbEAhgnY7z3a7bOpA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.6.1
Release Details
UpdatedNov. 4, 2026, 6:34 a.m.
Changelog

Fixed

  • An exported pool lost its multiple-choice and open questions when imported again. occ learning:export-pool writes the question type as type, but occ learning:import-pool-json only read question_type. Every question without that key was treated as single choice, so multiple-choice questions (more than one correct answer) and open questions (one model answer) were rejected and skipped with a warning. The command now reads question_type, type and typ, like the pool import in the interface. Without any type, several correct answers make the question multiple choice, as the import documentation always said.
  • % and _ in a search word for course material matched too much. The keyword search behind the AI assistant's course material passed them to the database as wildcards, so a search for 50% also found 500. They are now matched as plain characters, like in every other search of the app.
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-----
SignatureqlmJFTEKJuojHnQtpGBmvWQ2Kcl46WMRiKoYOT30QA9Wye0kTDNBmuWtr6gc6MI4mK3GzRCcNd57B3+cfpRJZnyMrKG8ZtiPxwnYfnb7mfxjUk+4k86qnoqb1lwFOXhm1NOsnEBPm5Zkaa+RjpsWsJhBUXamyZCuDiHt1xVqzew/5EW4qY9dOHDsJ50F2SidRlPbwJQcb4M1aXWvrlbwShCYa/Jg+AOB6xVSb+xdMrCusYKtzCxL9cobc6/maq5fu9UYoRJwKgeQAtLiJhHAFoM4SRL5FHmrD9VnmuxYKocsH7DCpOafTdCdRq23jjsCM8CXMygQUN3k/E5DJKn+9QAn/DeJQlBfGEr0HwpSwrgXnKoUfrsk0nfLlZhOlXZb1VG3hmQIClGcyt7znbS7rswWJGJKX40ky+hl1ZMnVhq1+CjjcpOZ5SUf0bS3I3YjEpnASbhJGRYv45sAgI8ZrUX8D7lQVq0aalyKPlqdYg/OezfItOgqY5n/Uj2/7eBwuTV+VflHu2TcaWUH7H7jBsNU4XZ5q3TrYLRb1O12YA3K3W4jp49gjBKr8zlScAReJZ5jg+3CfW3ODNIMbP67776n4XyxFdh7VaxkfNSQVWjHgbRaDmwV6S4xN0KnbGj0QybfnI2ZOD8tPVFGMUc8JKxyJsD/gJSIgkWdEJpG4Ho=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.6.0
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Added

  • Scenario / info text before a question (Codeberg #10). Every question has a new optional field "Scenario / info" for the case, situation or background a learner should read before answering. It is shown above the question in training, exam and exam review, Leitner, the smart queue, the daily challenge, duel, gameshow and adventure skill checks, and in the question list of the pool. It is plain text with line breaks kept, up to 10 000 characters, and it can be translated together with the question. Because questions are shuffled in every mode, the scenario belongs to the question it introduces: for a case with several questions, give each of them the same scenario.
  • JSON import reads the scenario from scenario (also szenario, context or info), JSON export writes it, and occ learning:import-pool-json accepts scenario per question. A scenario longer than 10 000 characters is never cut off: the import skips that question and names it in the error list. CSV import and export keep their fixed columns.

Fixed

  • An existing question with two answers could not be saved. The edit form adds empty answer slots up to four, and all of them were mandatory, so the browser refused to save until they were filled or removed. Only the first two answers are mandatory now; empty slots are dropped on save as before.
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-----
SignatureQqfTt1TeDKTUved8X8Dcv5vLVqimMtZy6VYots+lshbLgmtW4pBxjKtv/Xj2FMn8rmJnIXe1wvl7S4L7kwFraAHMrFxFBhaPG0x25cRWeL/61ukwAxscqgGBp1BpViWBcXsEcS/+xWZXSRklusg0BrCdWQAMu9xmMgeXwEs1L5boFkhqQ9WPOQ+MZj4qs+1iRBwuS6eK+O46Al5kmngQtjQT/1cq59s772yP+8sX2kR1AVhViLaxqIt3dPntDE27CfaKxCBH4jOrkPqJaDmv4WH6VqAGCXM0cClJBgQKwVaOHe9FYJk2ae06zaHoXLggLWvhUAr66m4DjpcUsozd+61wZX+nhXpvgLKvgMxMwugzmw9Ju2WqV3o6qbCOaYuOBXO4kDa3E1zUKYlLav6llzfTL3jccwXwaf7D2Yb+dJsaPO30/2zFUQCE5ws+gRw0ybDDIF/uEFtC35L9VE/9ENDqhUUq5VAZ8xdJ85e3wIIhSmIz6x1e5M+8C0xeUiX3mlX1BJaWHaFSehP8h6ci1Jn52pIgcpMXWfCMOuipRTnHzBTZcIdWJ+u/zcjDF9wf5Rg/HgOCSNDtDv7wq/WK45so5TanGuhPQs7sN3V2ruECYc1O6Hu3x6PqsHEka2++1rG0jjV4Lc48V91B++OJkAKYpq9DB2/jw0HlU6aHq80=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.3
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Fixed

  • Passing a course and issuing certificates failed on fresh installs. Every compliance event is chained to the one before it, and the chain starts from a first entry that the database update was supposed to create. Nextcloud skips that part of an update when an app is installed for the first time, so on every fresh install since 5.2 the chain had no start: "course passed", issuing or revoking a certificate and completing a mandatory training ended with a server error. The first entry is now created on install and on update. Where the chain already holds entries but its start record is missing, nothing is created automatically, because a new start would hide the gap; the admin log says so instead.
  • Question translations did not work on fresh installs. The translation tables were renamed in 3.5, but the rename also ran only on updates, so fresh installs kept the old names and every translated question failed to load. The tables are now created where they are missing.

Instances that were updated from an older version, rather than installed fresh, were not affected.

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-----
Signatureo8yBMrNyZAgnKOXnfigFnYCVUX3xjMbPbI1CLqQBcIOteYVfDOdQky/G0+BtQ4u3P5UD0Fe6bBhqfJQgrzsv6fOUxDg8lUQ5XbeC3GIftD6mgGi2JR3tZcxgLxEyXdCaMyzmHiT3FotUiCqbR7uWFDp5/UAUrpgxk/8zew7px3lrH9vrfXnGNOHDWib74vIH5Sjz1HFYXJpAyuhaFUuyYvREB8+r4ZGHjG5M/RZSaYk7uQ9r2+JvDUx+6BRCG8FOg+chwkmio7C7fMcHPeCgOxoaOlYo0or0ABpRNE/E8OUryh6z288ToP9ygqTX/m2VWWsIMGo1R21D4IYgSWy2T7g2LeWlrsg8NBbCOxJMR8KuQ4hO3DP7sgEz7Aq3RDHCG0lz7b31XV1ayfIPUERC1eO33G+7n9PF1fBR0HTYd0izOiOvYQENI4NuzwWNwZiiVaawMZbUZUwxKtSca6jrcBqnbv6iYwVKrgfKIb7BSAQ+2Bg1Dja7fy48c7eY0YGn/hX4jYWS7+HprjzBf8FCxruPha4ckCeiLNOtPxzTwuxBDOY+slYHEg6Bvggv0Dx3xpoAl0D5WBpNxZjM96dMIOGelrGNLB2WLWUg0q2QA4hUQIVlBOKRjEC4uMopC1+YS8z8pAWVFIj2mV6tICTQu8VXGd6i7NMsqUKm8XTEAc0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.2
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Security

  • The weekly study note was sent to the AI provider without consent. A background job runs once a week for everyone who studied in the last 60 days. It sent their weakest topic and the questions they got wrong to the configured AI provider to write a summary note, and it never checked whether the learner had agreed to AI processing. It now skips anyone without valid consent before reading their learning data, and the note generator refuses on its own so no other path can bypass it. This only affected instances with AI enabled.
  • Outdated consent counted as consent. The server accepted any stored consent version; only the VirtuProf chat window compared it with the current consent text. The server now requires the current version on every AI path, and the VirtuProf chat and the course summary narrative no longer use their own weaker check. The course summary narrative is also rate limited now.
  • A failed consent save was treated as consent. If saving the consent failed, VirtuProf closed the dialog and sent the waiting message anyway, although the server had no consent on record. The dialog now stays open with an error message and nothing is sent until the save succeeds.
  • Follow-up requests are checked too. Chat-memory compression and the pool generator send further requests after the first one; each now checks consent right before sending. If the server reports missing consent, VirtuProf asks again instead of showing a generic error.

A larger rework of AI consent follows in 5.6.0: consent will name the actual AI provider, the consent text will describe exactly what is sent, and everyone will be asked once to confirm the corrected text.

Fixed

  • The app icon stood out in the app menu. Learning used a coloured icon where Nextcloud expects the same monochrome glyph as every other app. The coloured logo is still used on certificates when the instance has no logo of its own.
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-----
SignatureDYtj4ULUbhWv/Own6XC4wfIshuUj8ND/dLsFst2krSvsBCs/c/lfxk81IpNAFtp5nCp7ihdBciuLw7XuQ7sfKUImWrXkE0sY2V38tD1XJWVFOpXIGBp0iDCVlxzUPFTWUuH+Ga9ItGIhUfIricBT70nkbcCN3FhTTLQqAYb89OsWHzV0DKiYLFTA3FD3ubeCahJBItQkfK0aGb7QZMKE8C3yGwAhPjFHDYXKyqiBlwbUVzjOTK7sZoWFkamh9ze9f7KwHZnvSEwUdnZmUyftTZMrJRycsPVA8cwuDNubH0Mbhw5/KMyeU7Hm65M2BJFAVm/8QPnaTSTf30RBNwi2es/DJ/S5IrRurWLVFvSXlEpAmHG82CDeN+sq6b+EF3NIjzBA8FNnfmbfG95rUYGImV9468yHSGkPkiyiMBqRj+efNImeUlhxeUdIXqMhVzB7DOFo4c3DzieKsKurmdcKCkoNCoEzh2Qybl5awSuSV9RUL5zpYVZ26FsH1hRLLu9yZOmDYHagkPFXw5aG6zazn3qSSRnikLI4kxrM3LtFXTUdlx3CxCcXczsWLkDL++nUMDxJP6p9LsbBrgple1tgGdfx5sMdKOIsJQiYF2gBJKrvcLjyji1M0PjJIJjOVpAuda/tdgcRY+1EYzkdcvxogW4Mh3Me45cLUK/EVYbIQ1k=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.1
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Fixed

  • Tool selections were saved but never applied. Reported again on #7 after 5.4.6: "regardless of the selection, all tools remain visible". 5.4.6 fixed the settings form, but nothing read the setting where tools actually appear. The course's learning space ignored the admin's global selection entirely, and an empty course selection ("every tool off") was read as "no selection" and showed all eight. Both levels now apply: the admin's selection, narrowed by the course's. With nothing left, the Tools tab disappears, and the dashboard drops its tools link when the admin has switched every tool off. After saving in the course administration the learning space updates without a reload. The unit tests for the learning space had carried their own copy of the old logic instead of calling the component, which is why they stayed green; they now test the component, and a rendering test counts the tools a learner sees. Verified in a browser on Nextcloud 33.0.9 with MariaDB 10.11, before and after.
  • The pool rules dialog showed every pool as required. The server sends the flag as 0/1, and the dialog read 0 as "required", so the checkbox was ticked for supplementary pools too — and saving any other rule in that dialog marked the pool required again. Found while adding the option below, which depends on that flag being right.

Added

  • Practice exams from required pools only. Follow-up on #9: a course may hold supplementary pools for self-study that should stay out of the exam simulation. The practice exam settings have a new option, Use required pools only, which draws questions only from pools marked Required in the pool rules. If the option is on but no pool is marked, instructors see a warning in the settings and learners get a clear message instead of "no questions". Tested on a fresh install and on upgrades from 5.5.0, on MariaDB 10.11 and 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-----
SignatureuJxsLjiKbsbrXM7v7CAn0FEqCEubH+3VTYxJgBTLm0Z4TxBY3sgcSV9+dDeV24fiHYL18f4tzB+013jku9suSZ67tZs+0cvzzhP77wy2LcRrV4VI6yFFOtmgS2HU+bc07Lrv/nIQz0HtcLfZjbenU1FyBRSUpWFGysQ8vuEabKS8m2Jneto113Qcty0YOWGqGoBI/MoQiaRlqM8XcF4kRodtPNmHxlGXUqJP/hO3tZP7WNIVBeDNz4p7TlpPEfjinuHniO2Gc6tmUK4b1B9BiCFzgXbxgV5ylzDgrbwbiqb2diqPezQFUaoIzcPshhR9602h1sL3BWi4McyvvDuEccXIxFLlvZYlH/O85yepBuzyKNsXquId/axgnKOQUJYWL0jPnat/yyUa+7MSRGVsYYro8xrVG4wGZD2sxXzDtolNd9F/gsg+4OR2lU5hUKOfBTuOp+cwQ/EXwX3wcyc7QszOvH8EDN0g24gC9dlB5Cs0ge6cybEx92RoWkE7bHf4CTe5FBhvSu+eRg1CTky1QS/x8NNsLbT4fDJvoID3QSwo+sj8Z9jUMN0gCT1qhu0iiXv3g0hQmnOcZhFwnmjoVNIw6HxNZlm+SZ4ceWjR4g/QZcPgmb7i0uVwR1gkBqJ3HOwc2fMh9VoEFqZ93SvRbTbB4t/eeSok7PEip8khvgY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.0
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Requested on #9 by a team preparing aviation radio courses: until now the exam mode assumed an IT certification.

Added

  • Practice exams per course. Requested on #9 for training outside IT certification: the exam mode only knew the fixed CompTIA presets (90 questions/90 minutes and 45/45, pass at 720/900). Instructors can now offer a practice exam in a course's Administration tab and set the number of questions, a time limit or none, and the pass mark in percent. Learners start it themselves from its own tab — no pool to pick first — and every attempt draws a fresh random set from all of the course's pools. Afterwards they get a review with the explanations. The CompTIA presets stay as they were; the practice exam tab shows even where the preset exam is switched off.

Practice exams never count towards a certificate: they are marked separately, and the pass evaluation reads only the other exams. For the same reason they are left out of the locks that keep an exam from being used as an answer key — an untimed one left open would otherwise block training and explanations on its pools indefinitely. The reverse direction is guarded: while a certificate-relevant exam runs in the course, a practice exam cannot start, and an old one withholds its review.

The pass verdict is decided on the server against the threshold stored when the attempt began, in integer arithmetic (74.6 % does not pass 75 %), so a later change by the instructor does not rewrite past attempts. Tested on fresh installs and on upgrades from 5.4.6, on MariaDB 10.11 and 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-----
SignatureQkyHXEoC5fAsNey6cnMRGCQpGB28CVQZ/54fNM1YfcAZEzfCZD8MoBlqlqq0ZhQli+CIHm+Te6xRdvA1lCrcnLODGjejcTgq/6+iXgcuSvJgd7Ev7IDm2ltRGUyq6j9DD3u85EfCvI01h37mIAWLYYsLBUP2C54qt9jU0rgyS0n0E1cp6r0+uGtd0ty/dHQwAycEmvlhAmyN9eOQXphbMK7sIItgBCnW0ykXEdxgKhCkpgcmMqxpSyrd1Ub4/lcLVoQ2JPxWun7/E9JVhPZ6XgpaRrrHlsAx0iQ/KBoKnT33jmGntcaz72vXBQZt5qWH3qkZoxinfpJvUDLE4VjvzMPyIHFqOBIZqA+HvCYMUnEpKAEuLy9AEwewLEnXqGYAH+CqpAojOtjaPHtWzd0oI8+CFEYt8VTfZS5ZD+ueMbYl/ARtq91MozvwjggM1UHfzeTybtnlJC77yYMxeqqQZtSupAS/GUboSIG3wu3PfD+x1X7PW5rJo+m3lR7vJ4Oz8SEIPNlt/s3jX+5lainSUAtOfYp30fCR0IcmkzmbajL+m7oBIqWQdSvxFqTUOr2+SmOmVERpXTikDC4X0XXe4cM1pbOiGolQqhc5RI2FC6c7aYcOT/rple7DLkKiT/I76LyqT7c/SE0NpJXv5N3DDAmuiAMti+PlreU3TiuTmSU=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.6
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Both reports came from the same install on Hetzner Storage Share: no shell, no occ, and MariaDB. #7 found a course view that failed and tool selectors that never showed their state; #8 found a false audit alarm and a certificate setup that required a command the host does not offer.

Added

  • Certificates can be activated from the admin page. Signed certificates need an issuer signing key, and until now the only way to create it was occ learning:cert:init-issuer — unreachable on managed hosting without shell access, where certificates could therefore never be enabled. Reported on #8 from Hetzner Storage Share. The certificates notice now has a Generate signing key button.

It only ever creates the first key: if one exists it refuses with 409, because an existing key may already have signed certificates. Rotation stays occ-only. Creation runs under an exclusive lock, so a double-click cannot leave two active keys — a guard the occ path now shares.

Fixed

  • "Checkpoint overdue" on installs with nothing to checkpoint. Reported on #8: the warning appeared next to "No checkpoint yet" and "Events since last checkpoint: 0". Overdue was measured from the last checkpoint, and a missing one counts as timestamp 0, so every fresh install was overdue from the start. The same false alarm followed every quiet week, because the weekly job deliberately skips when no new audit events exist. Overdue now means an audit event has waited more than 8 days for a checkpoint — which still catches a job that is not running.

The warning also pointed to occ learning:audit:verify, which checks the chain but creates no checkpoint. It now names the actual cause: a missing signing key (checkpoints are signed with it), or background jobs that are not running. - Tool selections appeared not to save. The selectors bound :model-value on native <input type="checkbox"> elements, which have no such prop — Vue rendered it as an inert attribute, so the boxes never showed the stored state. Every tool looked unchecked, clicking an already-enabled one changed nothing, and saving wrote all eight back. Reported on #7; four occurrences fixed. - Fresh installs never received three course tables. Version009900 does its work in postSchemaChange(), but a fresh install executes only changeSchema() and then records every migration as done — so it never ran and could never be revisited. Version010100 creates the tables in changeSchema() and copies any legacy rows afterwards. This is the likely reason a course view failed on the reporter's install, and why reinstalling the app reproduced the problem instead of fixing it. - The certification toggle always saved the previous value. It listened for @update:checked, which @nextcloud/vue 9 components no longer emit. - The active exam slot never displayed. The view read res.data.slot; the controller returns exam_slot. - Two buttons missed by 5.4.5. Both passed a styling value through a dynamic :type binding, which the new guard did not yet check. It now covers dynamic bindings too. - A missing database table no longer takes down an entire course view. Reported on #7 from an install on managed hosting, where oc_learning_course_curriculum_scopes did not exist. That table comes from a rename migration (Version009900) which deliberately aborts rather than creating an empty table over existing data — so an install can legitimately end up without it, and the app treated that as fatal. Curriculum scoping, paused-question overrides and announcements now fall back to "nothing configured" and log a warning naming the migration and the occ command that repairs it.

Writes are deliberately left throwing: reporting success for a save that stored nothing is the worse failure, and the caller has to be able to tell the user it failed.

The "is this table missing" check moved into Db\DbErrors rather than being copied — UninstallCommand has needed the same predicate since v5.3.0. It stays narrow on purpose: anything not positively identified as a missing table is rethrown, so a genuine database outage cannot turn into silently empty results.

This limits the damage. Why the table was absent is the "three course tables" entry above.

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-----
SignatureZ4KteEBRWfaEdZUSXUPV5e8sHFHO72aLZKRIFgUDMXjKJn97R3QodN3djq9hJLgcT0RUHxgZvfL4SeG2RvVYeCLOCIxcsGv6QV+yPoXw6w15Xay8CXBUExRuYRLKik+txLT0ttekRCHiTPrJU737R8CeodH0/dbItL6IJ/5JEOQv5iK5/8MLjL2wtJqBm2VeaE4H5nK/RYCvG/DNOTiFJgGgbhJlNhZcz+Yzg8LVyCCbRtQkU8MqagLTTAHbHCQU2+0h+0ommbO+Lcx6aHpvBKAic+VOB89YbbLoQpwtf5tirhjAM0FxT4EFK1OYTV1FfmRIz6wN1oIixcnPYSF6izmidK3jtTM+tBC6CqTUZ7lKtu+E+t7vWX+QrR2JEhVEi4FQ3bq/JoMUyGgnYxGvwB8p63T9SJeK0FEaI4fwgmHtA18xtjPUkUywE7CAp8DtVZ6Bgr163z1FUO8XOSsbPwUgh81VkIH/8NFHnReL5sBBDWBwFgsZ6iyMStv81OTZGglJBFyn9RjlV5HhrtprVLsDEr+G9BW9JOa/uM/dHFvuBxHGWdGadZ8QBlapI4ZD5Tz8GaWvPlvFYqeaovisViEcLN0TTSVN0+V95Cl2CZYb/4+fUKGPEhcTT5vEsb+mPYk/gCnz+NmqcSFp2ezR+GO2FWbx4EBSnbnlNFI8SEk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.5
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported in #7 by an external user on macOS, reproducible in both Safari and Chrome. The report was precise and its diagnosis correct — it just described one button out of 244.

Fixed

  • "Upload image" saved the question before a file was picked. The Vue 3 migration (380dd9a, 2026-04-03) moved the app from @nextcloud/vue 8 to 9, which swapped the meaning of two NcButton props: styling moved from type to variant, and type became the native <button type> attribute. native-type was dropped entirely. The v8 spelling was never migrated, so every button passed a styling value such as "secondary" to the native attribute — and HTML's invalid-value-default turns an unknown button type into submit. Inside a <form>, every one of those buttons submitted.
  • "Cancel" in the question editor saved the question instead of discarding it. Same cause (type="tertiary"), not in the report, arguably worse than the reported symptom.
  • The three save buttons only worked by accident. They carried type="primary" plus a native-type="submit" that v9 ignores; they submitted because the styling value was invalid, not because they asked to. They now say type="submit" explicitly.
  • All 244 NcButton instances were migrated to variant (the 222 outside forms were a visual-only defect: with variant never set, every button in the app had been rendering in the default secondary style for 5.5 months).

Added

  • tests/unit/NcButtonTypeProp.test.js — pins the v9 prop contract against a real NcButton render, and scans every .vue file so the v8 spelling cannot return. The four existing gates could not see this class of defect: it is valid Vue, valid JS and valid HTML.
  • tests/unit/QuestionFormSubmit.test.js — mounts the real form with real buttons and clicks them, asserting which ones reach a submit. Reverting the fix turns it red, on the submit count rather than on an attribute. Note the precondition the report already implied with "open an existing question for editing": while the required fields are empty, constraint validation suppresses submission on its own and hides the defect entirely.
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-----
SignatureX4k7LjqPFPx+SZ0KlaAr4FFFC9YdnYlt3mHWOnNskXRJI6+a7n2zgNlf6CwGnr09A8vJChj3uPg7y4MgnPZ4HLmtaNRPP05ol57+ev/o/2CZGElat0qGLwCHXE7/q71BVrcvzGXJOmc3B79R9iXjIFj+JR4qhWzZ1v0li3Fshz1mPwX2oQeAQ/7w5O1ffG5Vchk5hfJpU+X2phfuyBcxX3YpoUqNWOP6GAot8ENkl3znWoDidHICihBC1Szk25BWek0aUQng/ROo6zYn9xt8/d8xfIcJ0dBwvO7h29jeRDTxcRKFd4eBaXFnvesL58KZEC3vLSYduYzzK3ENccb8IXc0E8cqz0WXzI9V4l2cN5VaEYYpLO4I5++lwWqLPeHLAL6Gc543ihiDh04JgTuRA5VtrMGg6TCUpkmkWcdfKPEwuPWK+xq+cXBiHT9LyTqGsD6rfagyLGE2tnwnjni9++CIwj6ScsDU91CBFf/bZjDs21YouNXSpkkYsGFTuF4a3VTqFE1PuDcvZt02/1T+HX5Pwlj6BNm20O/81R2nuqtCU5kBgUbTLqtHimyufr0BKBWXclHZZEtKBOV/+iI6llWiSF6wDH/btNcgO9XlnGP3nK0S9x4GTTIXWqlsCZPILWByGYW47l6X7Fxb5fTgyZQMLz1kyV8om7Qs0q5+f34=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.4
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported in #6 by the administrator piloting Learning in Vinnytsia. Two of the three questions turned out to describe real defects rather than gaps in understanding.

Fixed

  • The assistant rendered German text inside a Ukrainian interface. VirtuProf does not translate through Nextcloud's t(); it resolves against its own bundled catalogue, and 161 of its 343 reachable strings had drifted out of that catalogue — including every FAQ answer body. translateVirtuProf() then fell back to the raw source string, which is German for most of them. 63 of the 161 were already translated in l10n/uk.json and simply never made it into the catalogue; the remaining 105 are new and now exist in all six languages.
  • The AI assistant was told to answer Ukrainian and French users in English. The system prompt's language map only knew German, English, Russian and Arabic, so anything else produced the instruction "If unsure, default to English". It now covers all six languages.
  • The assistant ignored the Nextcloud interface language. It derived the response language from the opt-in content-language setting alone, with a hard English fallback. It now falls back to the user's Nextcloud interface language, then the instance default.
  • Choosing Arabic as content language did nothing. The dropdown has offered it for months and the request sent it, but the server's whitelist did not include ar, so the value was silently replaced with the empty string and the request still answered 200.
  • "Create summary" wrote an English note for a Ukrainian user. NoteGeneratorService, which the assistant reaches through its file-intent path, carried its own copy of both the language lookup and the language-name map — and its copy was missing French and Ukrainian in exactly the same way. Both now come from one shared resolver.
  • The assistant panel header rendered three empty strings. Its translate helper passed the key where the function expects the language, so the panel label was blank and both the minimise and close buttons had an empty accessible name — in every language, German included.

Added

  • An instructor group setting in the administration page. Members of that Nextcloud group may create courses and question pools without Nextcloud administrator rights. The mechanism is not new — it has been learning-instructors all along — but nothing named it: the admin page referred to "the configured instructor group" without saying which, and the manual claimed the instructor role was per-course only, which is wrong for creating a course in the first place.
  • The five textbook fields in the pool dialog now explain themselves. They are optional and only pay off when a pool mirrors one chapter of a book; the dialog says so, and each field describes what belongs in it.
  • A fifth i18n gate (scripts/gen-virtuprof-strings.mjs --check). The VirtuProf catalogue is now generated from l10n/*.json rather than hand-maintained, and the gate fails when the two disagree or when a reachable assistant string exists in no language file. Verified: removing a VirtuProf key from all six language files leaves gates 1–4 green and turns this one red. The catalogue grew from 252 to 429 keys and lost none — keys reached through vt(<expression>), which no literal scan can resolve, are carried forward until they leave l10n/de.json. --list-indirect names the 22 call sites this protects.

Documentation

  • New manual section 2.3 on letting teachers create courses and pools, a corrected role description in section 1, a field-by-field table with a worked example for the pool dialog in 3.1, and two new troubleshooting entries.

Internal

  • deploy-prod.sh --stage-gate now copies appinfo/ into the analysis copy. Without it ClassResolutionTest — the regression test written after the AIController/AiController outage — errored out instead of running.
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-----
SignatureDLyhhVBf9XRwBNeoyTKog1TtpFna/YIjmDn+ZjlT9XdOHDkP+/lCeH5/llEg9T1YqKUzejWB6zRtksExb7/DCODs5W8r2AiSVkO1moPld7QVcjHOQXWZ7lXKqQ8FIDPKy6Xfa6QNbGc3HE1J0SGMfW315PV07TL074azidYLszcQzuVsoPcIir/FLptx8pdLReN2G//GucWdoGeAR1djjn4YfdFlCAE0eDuJnqi3dChkin0PMYFTbdK4uLqz6ocEkHcpjaw+0JbSe/7FZcoAkmEEHiV9rBbj9uikbBj/k1wYV8h4OD9TGJpK595NQ56GC4Py7FaCTD2mMLvjDiMsMYVq63hffv5xzwn0RPmAwosGImUpapd3ft5VTHuY+yP5prrrtNadF9Mm45KA0mPKrIJHOwey8xqyPfMl7N0RlhWBWwKbae436EQgTLifLdo4A/nz2gR/Fix58LLyQoHAKGKE/wdQ5jDrI5Dv7xA4cBzgcPeOC5S9vxUuAuKXuTQemliU9065EUw9nv+rTdTypx7CIzgVHtnjlXIapAQilENk1uLrg2b8AZD4PVXLq6UW1VLyO/H02XYKRuw67R4ZMbqg0tszrhamlCiGJB7WbvWbxP9NTfI0cFHZkq0dlRCUcUgIlQYj772fKw5t/cTHIJky8l9sBG/ObqWJVEawpC4=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.3
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Found by clicking through the 5.4.2 deployment: the wizard's "Skip" button sat at top: 16px, directly underneath the Nextcloud header, whose avatar button swallowed every click. The splash screen carries its own skip link at the bottom, so the first step looked fine — but from step two onwards there was no reachable way out of the introduction at all.

This is not new in 5.4.2; it has been there as long as the redesigned wizard. It is the sharper half of what #5 describes: users could not get rid of the introduction even when they tried.

Fixed

  • The wizard's skip button clears the Nextcloud header and can actually be clicked from every step. Verified in the browser: a click on the button's own centre used to be delivered to the header's avatar.

Added

  • Two browser tests that would have caught this: one walks a new account through the introduction and asserts it stays gone after clearing site data, the other asserts the skip button — not the header — receives a click aimed at it.
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-----
SignaturetBYdgRNTvcdREMCynplVM4OPCgWQck7GP+w3btJG710KKvOl1+YwKqBTcmUk3HoCwy+ws6BVOljRjCfJumhvCzPIIFpjs4fEsLskZyp7hueeRx9NTxCDe1fcWuZAA593HD1aDmrURuBWULQ09DRRcngYm3eD8X1yyr1oBvcwtFHkm+zMDOru484j4/Etl/R87I4vcA25ynoI1Rt8yxso52ZR3PFOb8Y+N4ghd7YlP01uffQAUlPEwQCrXwLXED7x6Z+ocSVcHVrmMFWx4f6ElksbV3UlIHd3ewB72t/kWNqCRqSTIf9L6DbyTb5MKFWwgBXRaP4vRbzZCrpukutZ5FprnR8XQ3KWNXfxJ4yW6nNYiZRYPuWu+sBKChWlDBa9RMgYnPv+ePV+H2EgW8NvI/DAXttcZDTsoFwZ0q/epmCuprdmcBBRefB3ysufGzzN7jQeVBEBr7dEkOtfadXpaLsLk03oQEu+1lM6Re7yPxYJukzWY3oG8tZXu92Q4TSnIAaSTAXP2+p/3wOEnm2u5r8dOnDQv3jUsSuJaGBBGRpIeTx1RAv2cWI698IAw3QJz1BQwnXpKdQei14hIRwacnB5mpJzj/N9cRmiGhQFrPaiNM2pcXoQGpLS73sa1p04Pl4bBrHFFqL3HzFqyKVGqhpH8kBMtR/NH1xcMebNYXY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.2
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported by the same administrator piloting Learning in Vinnytsia (#5): the help wizard reappeared for users who had already been through it, and its introductory questions ("How many bits are in one byte?") were beside the point for staff in occupational safety or HR.

The reappearance turned out to be three separate defects, not a missing setting.

Fixed

  • The assistant's introduction never honoured "no thanks". It read that setting from a response which does not contain it — /api/profile/telos/status returns the completion flag and nothing else — so the check was always false and the introduction started again on every single page load. The setting had been saved correctly the whole time.
  • Whether the wizard had been seen lived in browser storage alone. That is scoped to one browser on one machine, so a second computer, a different browser, a private window or cleared site data all brought it back. The server is authoritative now; browser storage remains only as an offline fallback.
  • Skipping the wizard recorded nothing on the server. It counts as a decision now, like completing it.
  • Instructors were shown two introductions in a row — the wizard, and the slide tour the moment it closed. One introduction, one decision.
  • Existing users are marked as done during the upgrade, so the update itself does not send a whole instance back through the introduction.

Changed

  • Instructors now see the full nine-slide tour inside the wizard. It used to follow the wizard as a second, separate introduction; folding it in keeps the content while removing the duplication. OnboardingIntro is unreachable for now and becomes the voluntary tour in 5.5.0.
  • Declining the assistant's introduction now puts the assistant in its reduced mode instead of leaving it in an undetermined state.

The introductory content itself is being reworked for 5.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-----
SignatureRWPqQbdfMQs4sWt1fQY2J12frNlvsBfyyxZVm5GD+QHIFuCDmqmhsunLgzv2zfzWn08hoinqIAE7hyOYf42Z38K17/vpiYxSnsltjK5f1gP1gomdeKwgGrTRpduOfDqXbH6KOGx82opBm1cjV6Whi2FVJrwCNE/MFwOzhdwshYHMWlOxVnCUDvm2iHfveakQ2fKbC7BAR3Sm8YtD/eUqsz/hNiGOZcCY71xo+LpUJge5bY18D9kz8GpXiwLhf8Ivzb5Dfj1b1xrwIS6WO19i1tgLpJ7adJXftyUv++o2DaPRLw1O3IcV0UUmmgo9GeDaq/1ZCtav5lu0r1O8crLQ1uSqZmwyzIkd/UG5feTsLC3ttF5/DA9nBhup7Gd6nIwluVKjaMUfyfmcypFS428rtlNefyJjgkD5gOi0aZH0GVE4cnEEkh3bzebPTLXHoTVjZ6SJIjYxQDHfRHJxObO4GCFuc/Fnq+MIGEySLlubt9q7Ja0GyphBmO1Vw3Z0fiCT24Ny6PrIpknjQcuUvb7/la6txUzB0e+IWGLm5YJf6FMZMgBSt4ZKOtUwONCrZ3RDHYIYLhtunfE1wCJNRt0ammhpTjUz99bjmQABkYfAeTQvznmZBzTi7PoEDyuHLm5sAPByjNhpySHDfe4nrLfArK30dmVoyqJgoBVve0+b79c=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.1
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported by the same administrator piloting Learning in Vinnytsia (#4): one error and one warning in the Nextcloud log. The warning was what it looked like. The error was the visible edge of a defect that had been silently discarding onboarding results since v4.2.0 (4 April 2026) — seventeen releases.

Fixed

  • The onboarding wizard could not save a profile, and lost two more things while failing. OnboardingRedesign.vue built its own request body instead of using the shared payload builder, and posted '' for the list fields where every other caller posts an array. TelosController::saveTelos() declared those parameters ?array, so PHP raised a TypeError inside Nextcloud's Dispatcher — before the controller body ran, which is why the controller's own try/catch never saw it and the user got a bare 500.

The damage was three times the reported symptom, because finalize() ran three steps inside one try with an empty catch: 1. the learning profile was never stored — onboarding_completed stayed false, so VirtuProf kept asking for a profile the user had already filled in; 2. the AI consent was never stored, even for users who explicitly opted in. Since the universal consent gate in v5.2.1, that locks every AI feature behind consent_required for them; 3. the selected starter pool was never imported — the wizard offers a pool, the user picks one, and nothing arrives.

None of it was logged. Three fixes, at three different levels: - OnboardingRedesign.vue now builds its payload with createTelosForm() / buildTelosPayload() — the same util PersonalSettings.vue and VirtuProf.vue use, so there is one payload shape in the app instead of two that can drift apart again. - finalize() settles each step independently and logs a failure to the console instead of swallowing it, so one failing step can never cancel the other two. - saveTelos() / updateTelos() accept array|string|null for the list fields and split a string on the same separators as the frontend (, ; newline). A malformed telos payload now answers 400 instead of raising an uncatchable dispatcher TypeError.

No migration is needed and no data was corrupted — nothing was written, rather than something wrong being written. Affected users recover on their own: the profile can be filled in under Settings → Personal or through VirtuProf, the consent dialog reappears when an AI feature is used, and a starter pool can be imported from the pool list.

  • Activity\Provider::parse() threw the deprecated \InvalidArgumentException. Nextcloud replaced that contract with \OCP\Activity\Exceptions\UnknownActivityException in NC 30; until then every activity-stream load logged a deprecation warning, and from NC 39 it will be logged as an error. Both throw sites migrated.

Notification\Notifier had already been moved to UnknownNotificationException — the same NC 30 cohort — and this provider was simply missed. Each throw site now has a test, so the pair cannot drift apart again.

Tests

  • tests/Unit/Activity/ProviderTest.php (new), tests/unit/onboardingTelosPayload.test.js (new, covering both the payload shape and the step isolation in finalize()), and seven cases added to tests/Unit/Controller/TelosControllerTest.php.
  • Every one of them was verified to fail against the pre-fix code before being kept — the controller cases reproduce the reporter's exact TypeError, and the finalize() case fails with expected [ 'telos' ] to deeply equal [ 'telos', 'consent', 'pool' ].
  • tests/Support/PhpUnitStubs.php gained OCP\Activity\IEvent, IProvider and UnknownActivityException; the PHPUnit bootstrap deliberately does not load Composer's autoloader, so OCP types live there.

Also fixed — found by auditing for the same bug class

Codeberg #4 was one instance of a pattern: a frontend that posts a body the controller does not read. Nextcloud's dispatcher does not reject unknown keys and fills the missing parameter with its default, so the request succeeds and the app quietly does the wrong thing. PHPStan cannot see across the HTTP boundary and service tests start after it, so nothing caught these. An adversarial audit of every such call site turned up four more.

  • Saving a course schedule deleted it. CourseController::setSchedule() takes entries; the curriculum tab sent schedule. $entries stayed at its [] default, and ScheduleService reads that as "replace the schedule with nothing" — it deleted every row, inserted none, and answered 200, which the UI reported as saved. An instructor editing a curriculum schedule lost it, with a success message.
  • Reported questions lost their identity. SupportTicketController::create() reads questionId/poolId/courseId out of context; VirtuProf's "report this question" sent them at the top level, so the ticket's structured columns were stored as NULL and the question was identifiable only through the subject line. The same component already built the correct shape for its other ticket call.
  • "Explain the question I just got wrong" did not load that question. The chat request sent questionId; the controller declares lastWrongQuestionId. The value was dropped, so the retrieval context never included it. The richer questionContext usually masked the defect.
  • Two icon URLs were relative where Nextcloud requires absolute — Activity\Provider::setIcon() and Dashboard\LearningWidget::getIconUrl() (whose interface says so explicitly). Desktop and mobile clients do not share the web root, so those icons did not render there. Notification\Notifier had already been doing this correctly.

Each of these is now pinned by a request-boundary test in tests/unit/requestPayloadContracts.test.js, verified to fail against the pre-fix components.

Also fixed — in the profile endpoint itself

  • An emptied help-topics list was silently preserved. saveTelos() forwarded the list only when it was non-empty, so [] and '' both meant "omit" rather than "clear". Emptying the textarea and saving kept the old value — and since help topics are published to classmates through the classbook at course or public visibility, a user could not withdraw a topic once shared. An omitted field (null) still means "leave as is"; a supplied empty one now clears, matching what updateTelos() already did.
  • A malformed updateTelos() payload could half-save. Skipping a non-array telos and carrying on meant {"telos": "oops", "bio": "changed"} stored the bio and answered 200 — a worse outcome than the TypeError it replaced. It is now rejected before any other field is touched.

Known limitations

  • List values are not round-trip safe: a topic containing a comma, semicolon or newline is split on reload, because the frontend has always joined and split on those characters and there is no escaping scheme. The string path added above follows the same convention rather than introducing a second one.
  • Users whose onboarding failed before this release keep a local "onboarding seen" flag while the server has no profile. They can fill the profile at any time, and the assistant prompts students who have it enabled, but there is no automatic reconciliation for instructors or for users with the assistant switched off.
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-----
SignatureD3aST42eiKf/nYaI5/U8YFgvDZ7RMjOBDOb+wZpO4QpWxrslmpBK86vnPCTj2glzExY08m104traCr2c59xfe/MYRWc3xfVgeBgyLQa0/KTJUO2ecJzP0pwMb24ybNYB5vSKq7ONgZPvv/9WNoo+BKPzos3CT2xpO0+0P0oKGXyErMCU2kJnG7kfTr1ih2jzurKy3vEXGsj43Hth/skA+DNRToPZH6fMyjBYkhaK0ezVO4+Sd18jImh7kAdEl1uX429jC70cQCrurkT8+4KPax0QCrOSza7Va7qHAnM7NuqHl+Yn0lNQwvUSTlnP3dpkUVOHg/e2REyROD76NiPcv7RX1vQH1Yli8eQ6MOfg2wM7gq9sCcJ9yG83VJ9wR+CZqDHbBYe7hm09p70J1Hrx3p1vDmYVhl8CapA1H1tZz58YkoTwbycwbesy6xbtM+nG7m20AjE09s9Ye3Sg+vWWu7bQCbKEZCt4OXxEHwQWXX6VQ0toFNDm8ZIxFhnyMG4C7Z/kIC51h5wASBCE+ZNyTmhDZogBEfbJrdxu+Zq8JIPq++s1IBUUHUFUvBbwP5iydFF2Ftcu4EzbxvwfpqKJNiAMbCJFOUMPYFAXL4ToSLPzqZDtVZpzki9b44wtyJ0io74iu4U1ohgD/8erbwz21qXnzasdTwxtyRPNfBejhLo=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.0
Release Details
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 a.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
UpdatedOct. 15, 2026, 8:25 a.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_announcements → learning_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
UpdatedOct. 15, 2026, 8:25 a.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
UpdatedOct. 15, 2026, 8:25 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.10.0
Release Details
UpdatedNov. 13, 2026, 9:25 p.m.
Changelog

Reported by alphalib-g in GitHub #35: a small organisation that certifies its people every year found that learners passed and never got a certificate, that learners enrolled by typed user name could not see the course, and that learners could not open the course materials.

Administrators, please read: certification follows a new rule. - The certificate is issued right after a counting exam. Before, it was only issued when the learner opened the completion tab, and that tab was hidden unless the course summary was released. - Every required pool needs its own passed exam. With required pools, each of them needs an exam started in this course's exam mode at or above the course's Minimum score (%); the best attempt per pool counts. Without required pools, the best exam of the course decides. Leitner mastery no longer counts towards the certificate. - Exams taken before the update count under the new rule, including the former 45-question "Practice exam" card, which always counted as an exam. A learner who already passed but lacked Leitner mastery receives the certificate the next time they open the course's completion tab. - Practice exams and the new pool practice runs never count. - Instructors of a course get no certificate in it (owner and co-instructors), also not through the student view. - A recertification counts only the exams of the new period. The period opens once the certificate has expired and the grace days (default 14) have passed. Before, last year's exams renewed the certificate without a new exam. - Every PDF and Markdown file directly in a linked material folder can now be opened by all course members. Link a folder that holds only course material; the home folder is refused. - Exam slots are removed (Administration → Exam Slot). The setting said learners could only take the exam during a slot, but nothing on the learner side ever checked it. - A repair step corrects member ids. Course members and pool shares stored with the wrong letter case (for example "Alice" for the account "alice") are rewritten to the real user id. A row that would then duplicate an existing one, or that names the course or pool owner, is deleted instead. Rows of deleted accounts are left alone.

Certification

  • Issued right after the exam. The exam result screen judges a counting exam by the course pass mark and says "Certificate issued" with a button to open it, "Pool passed — still required: …", that the pool is not required for the certificate, or the pass mark and the retake rules. If the certificate check fails, the result is still saved and the screen points to the completion tab.
  • Pass rule per required pool (see the note above). Only required pools that are still attached to the course count; if none is left, any counting exam of the course decides.
  • The completion tab ("Completion & certificate") is always shown in a certifying course. The course summary switch now only releases the statistics. The tab shows the pass and certificate state (issued, expired, revoked or issuance failed), the required pools with the best score, the pass mark, the validity, which exams count, and the retake rules, and offers a retry instead of a blank tab when loading fails.
  • A failed issuance is reported, not shown as success. Without a usable signing key the learner reads "contact your administrator". Under Participants → Completion & certificate, instructors see who passed without receiving a certificate and whether certificates can be signed at all.
  • Recertification counts only exams of its own period (see the note above). The same applied after revoking a certificate: exams from before the revocation reissued it at once.
  • Instructors are never certified in their own course. The rule is still checked and shown, but nothing is issued or written to the audit trail; the completion tab and the result screen say why.
  • An expired certificate is named on the result screen, with the date and the note that a retake counts once the renewal period has opened after the grace period.
  • The certificate settings warn when the course's Exam mode is off, because then nobody can be certified.
  • The certificate notification opens the course's completion page.

Enrollment

  • Enrol people and groups by search. Under Participants, search for people or groups (Nextcloud's sharing rules apply, at most 25 results), choose the role and add several at once. Choosing a group enrols everyone who is in it right now; people who join the group later are not added. The result names who was added, who was already in the course, who was not found and which group was not available. Existing members keep their role, and a different role picked for them is reported as not changed. One step enrols at most 200 people, 20 groups and 1,000 accounts in total.
  • Members are stored under their real user id. Typing "Alice" for the account "alice" stored "Alice", and on PostgreSQL that learner never saw the course. Pool shares had the same problem. Existing rows are corrected by the repair step (see the note above).
  • Clear messages instead of "Failed to add member": unknown account, already a member, not an instructor of this course.
  • Group privacy: when sharing is limited to own groups or user enumeration is restricted, a non-administrator can only enrol groups they belong to, and a typed account outside their groups looks like a missing one.
  • Enrolled people get a notification that links to the course (not when you enrol yourself).
  • The course form's group field works. "Enrol all current members of this group" enrols the group's members when the group is set or changed. The group is checked before saving (it must exist, be allowed for you and have at most 1,000 members); otherwise the course is not saved. Saving the course again enrols nobody, so people removed by hand stay removed. Before, the field did nothing.

Exams

  • Exam cards follow the pool and the course. The exam has up to 90 questions from the pool (all of them if it has fewer), 1.5 minutes per question and at most 120 minutes; it is disabled on an empty pool. Before, every course offered fixed CompTIA cards (90 questions, 90 minutes, 720 of 900) and a 45-question "Practice exam" that in fact counted.
  • The CompTIA 720/900 scale is only used in courses without certification. In a certifying course the result is a percentage against the course's Minimum score (%), the same rule as the certificate.
  • New "Practice run" card per pool, shown only when the course offers practice exams. It uses the course's practice settings (questions, time limit, pass mark, attempt limit, cooldown), ends with a review with explanations and never counts towards a certificate.
  • An exam started from a course is composed by the server: at least 45 questions (or all eligible ones if the pool has fewer), without a question-type filter. Before, a shortened request could turn a single correct answer into a pass.
  • The exam setup names the pass mark when the exam counts; texts no longer promise a simulation of the real CompTIA exam in every course.
  • Exam slots are removed (see the note above). The exam date only drives the learners' countdown and reminders, and its help text now says so.

Course administration

  • Learners see the course schedule as a timeline on their dashboard. Before, they got "no schedule" because the schedule was readable by instructors only. The timeline matches entries to pools by the pool's chapter key or name and shows a load error with a retry.
  • Schedule editor: a chapter ID column with suggestions from the pools' chapter keys; a repeated ID is named before saving, and a refused save shows the server's reason.
  • Every learning mode switch says what it does. The Leitner sprint setting only shows while Leitner is on, the adventure campaigns only while Adventure is on. A note explains that gamification is a server-wide setting.
  • New switch "Show simulator tools in this course" (on by default, so existing courses keep their tools).
  • Saving the tools, the sprint setting or the Talk room no longer stores a mode switch that was changed elsewhere without its own save.
  • The student view shows the course the way learners see it, without the instructor tabs. An instructor enrolled as a learner in a colleague's course no longer gets the Pools, Topics and Knowledge tabs there.
  • Help texts for maintenance mode, topics and chapter keys describe what the app actually does.

Materials

  • Learners can open the material files: file names are links. Only files directly in the course's current material folder are served, and files of a view-only share are not.
  • The extracted text is no longer sent to learners. The document list used to hand every course member the full extracted text of each PDF and the extraction errors; status, errors and paths now go only to the course's instructors.
  • The owner picks the material folder with a Nextcloud folder picker; the page says that course members can open every PDF and Markdown file in it, and the home folder is refused. Re-picking a folder that was typed in no longer registers its files twice.
  • Extraction is explained: with AI off, extraction status and buttons, the Knowledge tab and the learners' Swarm tab are hidden; with AI on, the page says that extraction only feeds VirtuProf. Without pdftotext the page and the admin settings say so, and "Extract all" leaves PDFs alone instead of failing them.
  • Learners get their own empty state, and "Learning materials" comes first in the learning space when a folder is set.

Fixes

  • Database errors when starting a training or exam were sent to the browser with the raw driver message (table and column names, SQL); they are now logged and answered with a fixed text.
  • A running exam resumes only in the course it was started in. Before, an exam started outside a course or in another course sharing the pool was resumed here and counted for the wrong course or none. Now the learner is told to finish it where it was started.
  • Practice attempts are no exam scores: they no longer appear as a pool's exam score in the learning profile and earn no exam badges (quick thinker, exam ready).
  • A schedule that failed to load can no longer be overwritten from an empty editor.
  • Course materials that failed to load show an error with a retry instead of "no learning materials", and no folder, scan or extract controls.
  • The dashboard exam countdown skipped every exam date saved with a time.
  • Translations: the exam-mode hint was English in French, Russian and Arabic; French showed "Group" instead of "Groupe".
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-----
Signaturep1mTaRy9uh56bfMkWJoABvbRoR3wPat7BYtozHJHNhlitCAJTgX4d3t20IvYgB4jU5mbrlB2qAaQBlRjwDlyF7XveAJBqlrATVnocWVrX4B4In4pbot5i0RFpuw/i+TAdkUas6EtLIX9QfPH/kxBYMaxuZgoEjv2T4XwzP04z5fE/M1uTwsBwwMXfyPy8aLj8JT5Ca6HFIZSMyyylBqrSvMt2LIm7H/+BeMYYWSBckSkgNLaas5uDdxAIA1HauIS887udrCscVyYt/+GZwgmJ7oumkzsm1CPGndrykp6X6p3nXkrDp23NXP337ipW9pi+2w9GKwrhiy35TAcTj5+k6RWU2nKok9FFmjHVJ+4fwHvLOyWfX549A2Or3gmEBJmhV3E2EQY199M3EhNzyn5zJb6mKUcpXJxeukjWB64n1nlSh9JVlD6DOufpI5XZUyIlTzu0xl6uFsYFaFaXEIudw9S/DsM9lHRkl23To3u54EM7J+oYPwU3gdxqXk08GWdtlsuz3onenx9/cs2OTohepCl8JsB8ss5jUiYxAOHTBp6kLYCDfQM+8lXnVCbJb4XgwUSlzqkDlnZy+XLSOd5CBgNp2ikWlRSV/pH9vw4/wWfjYjWbIK2yQMqKY3yFTZBLAoL411eM6AMM1QVs5iugwtl4N+8WTDVMSFkNJaFXTA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.9.0
Release Details
UpdatedNov. 12, 2026, 9:33 p.m.
Changelog

Administrators, please act after the update: enter your operator details. The imprint and privacy pages no longer name a fixed operator. Enter name, postal address and contact email under Administration settings → Learning → Operator details (imprint and privacy notice). Until you do, both pages show a neutral note instead of an operator, and Learning no longer sets the Nextcloud imprint and privacy links (Theming). Links that already point to Learning's pages are kept and lead to that note until the details are entered.

Fixes from a full code audit of the app.

Security

  • Learners no longer receive the answer key with the questions. Correct-answer flags, explanations and interactive (PBQ) solutions in question lists, search results and translations now go only to the pool's owner, users it was shared with as Can edit, and the course's instructors. Learners and read-only shares no longer get them. Practice modes (training, spaced repetition, bot games) still show the correct answer after each answer, as before.
  • Exams no longer leak solutions through side paths. During a graded exam the question APIs withhold the solution of every PBQ type, including the grading data of command-line and multi-panel PBQs, and the cheat sheet leaves out pools with an open graded exam.
  • Adventure skill checks only use questions the player may see. They draw only from published pools the player has access to, accept only the questions they offered, and neither draw from nor reveal answers for a pool with a running graded exam.
  • Duel and Gameshow accept only answers to the current question, and the Gameshow sprint time bonus is measured on the server clock and capped at 500 points.
  • Course archiving and reading archive snapshots are limited to the course's instructors and Nextcloud administrators.
  • CSV exports of at-risk learners and the class summary neutralise cell contents that a spreadsheet program would run as a formula.

Changed

  • Operator details are an admin setting (see the note above). The imprint and privacy pages use only what the administrator entered.
  • Bot mode in Duel and Gameshow grades your answer on the server, because the browser no longer knows the correct answer.
  • Exams: answers that arrive with the automatic submission up to 30 seconds after the time limit are still accepted, and the exam submits itself 5 seconds before the end. Before, all answers of a timed-out exam could be lost (0 %).
  • Gameshow: when the 15 seconds run out, a missing answer counts as wrong and the game moves on for everyone. Before, Sprint and Elimination could stop on a question nobody answered.
  • Required Vimeo and YouTube videos report their progress. After the learner's consent the page loads the provider's player script, which tells Learning which parts were watched, so such a video can be completed and unlocks the course quiz. Before, it could never be completed. Nothing is loaded from the provider before consent; the privacy page lists the new category.
  • The nightly XP consistency check never lowers XP or the level. It used to reset XP from single training questions, streak bonuses, Leitner reviews, missions, goals and Gameshows overnight. XP already lost this way cannot be restored.

Fixed

  • Team lead dashboard: members with a valid certificate (without expiry or expiring in more than 30 days) are no longer listed as missing; expired certificates show as expired, with a reminder button; the newest certificate counts. Due and expiry dates show as dates.
  • Exam mode: Enter on a focused button or navigation dot activates it instead of skipping the question, and keeps its hotkey meaning after a mouse click; the next PBQ in an exam starts with its own empty answer; pool editors keep the solutions and their explanation translations during their own exam.
  • Required videos from Nextcloud Files send only the newly watched seconds, so they can reach completion.
  • Unified search works on PostgreSQL.
  • Course material search (RAG) keeps Cyrillic, Arabic and accented keywords and keeps the text directly under a Markdown heading; a sub-heading directly under a heading names the chunk.
  • Imports: pool_name is read as the pool's name; question type multiple_choice becomes single or multiple choice by the number of correct answers; the occ dry run counts only what the import would store.
  • Adventure mode: skill-check questions work on MySQL/MariaDB; a new skill check starts with an empty answer batch; bot-correction scenes no longer list the correct option first.
  • Games and leagues: a duel forfeit goes to the player who stayed; co-op players who were ready first leave the lobby when the game starts; ranking PBQs are scored in the spaced-repetition queue.
  • Leitner: switching the language during a review keeps the queue and the current card.
  • Course feed: "Load more" loads the next entries.
  • Exam reminders find exam dates saved with a time.
  • Maintenance mode can be switched by co-instructors.
  • Material scan detects duplicates per course instead of across all courses.
  • PBQ builder: applying an existing PBQ keeps the fields the builder does not edit.
  • Navigation: a pool opened from a course keeps its course context; the app works when Nextcloud runs in a subdirectory or with index.php in its URLs.
  • Calm display: the switch shows ON when animations are off, and the setting applies after every page load.
  • Learning profile: the history trend reads sessions newest first.
  • Onboarding: "Skip" on the Jumpstart screen declines the selected starter pool.
  • VirtuProf: auto-read and chat scrolling react to new messages.
  • AI errors of the note generator and the weekly plan are reported by cause (not configured, rate limit, temporarily unavailable) instead of a generic server error.
  • Admin settings: an exam cooldown of 0 minutes is shown and saved as 0.
  • Tools: the subnet trainer accepts IPv6 answers in any valid notation; the VLAN frame preview puts the 802.1Q tag after the source MAC.
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-----
Signatured0EiRXMmplgSSmtW8lYHyMhlK+d+z3FllLYfd6W5/KOI+d4zZ7TEvr3npFdou+eaiRCL+8VWyqTY5yjMt04R5txLiL37+A5ENjxDu1g5AmDsJjOqZiKI+KY5YqFP6tZUGOvd2t4stZVkdt8EFI6Wt7vyhLeMXiCTFU3aj6m/SXhf2E9TDiQT3n5fJI9CQcz1V7RHuWCQHQfrjBLbobxhDK5OiqKYdl0adhUJv/Ob2QZAm9sreFAxiN7o5ugYJ3UNePLmXF4aPqzvcAuWKwCmCRnh0EOBpx1VSucwj5eI0h4epBmSyZoitun3IBSMw4xhxFOdMgvhMejcfbEp+y8VXEGkIxZkCCHg3Xrcj+R4T1KX2YasObMlJhNhvA40gr+DLSK18y3UZvHMiOtwWKHzuhzESBJ0u8ZCRPaQr2kkg6yqwHRK7ZSmHJQioLnim6/F40xwUk6qmo4c5y8tiwtJfE9nCRsxwlwZ5evDa8i5d+LfNQvifILYt8OQTpBfee8qSeIKendI+wgOTVZ/mg30I3RIQV0GMjI+dThfzpK5/IfJooifQnQKu9H/LjXovMUUhNIY1wl8tVaO9MNMlGBpiZ/wb2AfrsYAPHk3vLnrLEwa8KDC8JZqNJZucqdpaMuegSrGE+yi525CQBQiCl1bokb4DVx9eQjt31D7JVS7aFU=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.8.0
Release Details
UpdatedNov. 12, 2026, 3:34 p.m.
Changelog

Reported on GitHub #34 by a self-hosting administrator who could not find where questions are added and whose JSON file did not import.

Fixed

  • Pasted JSON was read as CSV. Pasted text that is valid JSON now switches the import dialog to JSON by itself; broken JSON stays on the CSV tab with a note that quotes the JSON error. Uploaded files are read by their extension (.json/.csv) first, so a .csv starting with [Network] … no longer goes to the JSON reader.
  • Many common JSON shapes were refused or crashed the import. The import now also reads answers as plain strings (options, choices) with the correct one named on the question (answer, correct, correct_index counting from 0, correct_number counting from 1, a letter or the answer text), the documented pool file with one pool, and numbers where text was expected. A marker that names no answer skips the question with the reason in the import report.
  • The pool format lost its metadata in the interface. The pool's chapter fields, exam_key_prefix and the _meta defaults now apply to the questions in the order question > pool > _meta, the same rule as occ learning:import-pool-json.
  • CSV with line breaks inside quoted fields (as spreadsheet programs write it) was torn apart. Quoted fields may now contain line breaks, the separator and "".
  • An export did not come back complete. The JSON export now carries difficulty, chapter, handbook and exam key, and the subtype and configuration of interactive questions (PBQ), so it re-imports without losses through the interface or occ; ranking PBQs can now be imported. An exported multi question with one correct answer stays multiple choice. The CSV export (also the course statistics export) is plain RFC 4180 now, the format the import reads, so a backslash before a quote no longer breaks the re-import.
  • An empty pool was a dead end. It now opens in Manage for everyone who may edit it, and Training offers Add questions.
  • Wrong answers could be marked correct. A correct answer after the eighth was cut off before it was looked up, so the first answer was marked instead; a correct-answer list naming the same answer twice (such as [1, 0, 1, 0] or ["B", 1]) was silently merged. Such questions are now skipped with the reason, as are questions with more than 50 answers and correct-answer lists longer than the question's answers.
  • An unknown question type failed the whole pool. In occ learning:import-pool-json and the pool import, the dry run counted a type such as multiple_choice_extended as importable, then the real import refused the entire pool, valid questions included. Only single, multi, open and pbq are accepted now (any letter case; true_false reads as single, the historical multiple as multi). Any other type skips that one question with the reason, in the dry run, the import and the question import of the interface. Creating or editing a question refuses an unknown type; a question that already carries one can still be saved unchanged.
  • Out-of-range values. A difficulty longer than 20 characters failed the whole import with a server error; it is now cut to 20 characters. A chapter_order outside 1–9999 is dropped, in the interface and in occ alike. Answer texts over 2,000 characters are still cut, but the import report now warns. A CSV header row no longer counts towards the 500-question limit.
  • _meta.default_question_type: "pbq" no longer turns a question with several correct answers into an interactive question. Interactive questions accept text, question and frage as the question text, not only question_text.
  • Import errors from the server are shown in the reader's language.

Changed

  • The Manage tab follows the pool's edit rights. The pool's owner and users it was shared with as Can edit see it, also without the instructor role. Creating a pool still needs the instructor role.
  • The student view is visible. While it is on, a banner on every page says so and offers Leave student view; a role that could not be loaded is reported instead of silently showing the learner view.
  • The import shows a preview, for JSON and CSV, of how many questions can be imported, and why the others cannot, before anything is saved.
  • occ learning:import-pool-json reads an export directly: a plain question list, or {"name", "description", "_meta", "questions"}, becomes one new pool, named by the new option --pool-name, else by the file's name, else by the file name. The command now also imports PBQs, and a question's own chapter wins over the pool's.
  • Manual and import guide describe who sees Manage, all accepted import formats and the round trip export → import.

Added

  • Import pools in the Pools tab. Instructors can import a JSON file with one or more pools (the format of occ learning:import-pool-json, also a plain question list or an export, which becomes one pool named after the file) in one step, each pool owned by them. A preview from a server-side dry run lists every pool with how many questions can be imported and why the others cannot, before anything is saved. Each pool is saved on its own: a refused question is skipped, a pool that cannot be saved is rolled back completely, and the other pools stay. Up to 50 pools and 5,000 questions per file, with the same file-size setting and rate limit as the question import. New endpoint POST /api/pools/import (instructors only, dry_run for the check run).
  • occ learning:import-pool-json and the new import share one implementation, so a file gives the same pools either way. The occ dry run now checks every question and lists the ones that would be skipped, and a pool that cannot be saved is rolled back as a whole, with the technical reason written to the Nextcloud log instead of the output.
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-----
SignatureVrC4hI1zjAahOe8f5+nyNpqhRuV8zH8by5r8MhjccizM5rWKNF4I4J2ENiI4sh/Ja12panTqJuyWZc837YRnxukcjbMjLj1KxLoX0ZYXas8a37pkoJ8sIkXmmby8mGXsFjuQsvuxN2cvkMU/uU07gxxg3myFbQge3b+ND92bHCO9tl7IGyyCVTNIwtdQAL4ldaS6fONrUxlXMOA0c+FEEWGqhAHBNMwUX7ChacWOM446YR4jZTzEKckL5BBTqPjtg94W9eX8/vapA5ZRVvbfoAtKXWkMULpGGHcEvwwds+hBZ2bxvOuyqcJdCEgRVWD+Xp/aBVhDTqiq+4+d738sO8ILhFxchpcOIADHbcPyfO2RldGQZMxA8pD9CJu1dKHB+9mOg3oar9Dx4wF5dZGjYqew+/jo12pXSdSDc5PRaQxvbcYSzPQZwkl1118Tw9yFs441sFhCES7SQS7/fnM3bx1JECwXMA3O9oJq+ysQRsl2Nk1mI3+UvtQAXr5RgD26x3/F1HOWt99g92jEcOpPFFU1ZvfYYHO2G5NM9z1akY1ibgqCHsxETZjamI1KulFEWmhcJ9iYN9TXTP7WuO6vnqaj50ZjXWvXtwGfn9yFB/jjyYrQw6tdRIhSeZyqGtQeJdU5qMWdyZ2hl6RSl6n+7UiCP3Ie7bIuEUTBjcwKDRI=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.7.1
Release Details
UpdatedNov. 4, 2026, 8:18 a.m.
Changelog

Fixed

  • "DevCloud" no longer appears in the interface. The name of the author's own server was hard-coded in the onboarding ("Welcome to DevCloud!"), the imprint and privacy pages, the personal settings and the VirtuProf privacy hint, in all six languages. The texts are neutral now ("this server", "the learning platform"); the PWA guide no longer names a fixed server address.
  • The streak and user state failed on SQLite (/api/v1/user/state answered 500): the query used MySQL's FROM_UNIXTIME. SQLite now uses its own date function. PostgreSQL and MariaDB were not affected.
  • Audit checkpoint anchoring had a default host. Without a configured Forgejo URL it fell back to the author's server. It now skips the anchor instead. Anchoring stays off by default.

Changed

  • Notifications (toasts) come from @nextcloud/dialogs 8: accessible, and shown at the bottom start of the page.
  • Development tooling: PHPStan 2, PHPUnit 13 (tests only; the app still runs on PHP 8.1+).
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-----
SignatureHV8IxQAN0P9065NUlpA9bAX9eDV/BplS4KgVDTjS7map+w8Z/de0MYkfQ7uM/ypq6yVCSkO4u/JxyZO50joNh6sxAnfjZq/JJepaAeycRr55qrwxBRM4+/ScP5tVs+RYcZl8ZU0+eKWS/mcevp77wzqoPEaxqOe6p7ysKZjUyCTUfnLp+B8pI4fyt13ji/DC5meFijLB23brN0SMhDEhHxUh8ScKk+PRpSgzMzyNIUQALBiV7aB+TIYLPl9pbVKkHaNvt8PRVYyPwJgypdw1FxgnNR+X4wFlGK2mTjsuKroPKjkyzYkIUHokxNiJasX8O1NfrDaZOgOigCsM38cTXB1Vg/Xw04znmM4AYGg7q8u4Pze/DvDWNI4r8mGQg7tbnCUiwLhhD9iQauIlFadqjLumX+8a4z+KVOZVqGHbgNNrjXeZJTonIvQcjvhFUWFTbtGReO84IxQUbYaVlVo4pjxP7QB6v6yRLpr/RufmqzfGZ9qolSc4AVlyoM1SlBNHvYMaWdCzaTeqNZMcQlB5q5LqRXMm0N7RtkxzK24BUJiegAJ+9UWybizbAiYKLHFXXnv09XjHhUTgFXSviU2EfD1zscQuBvgZDwXR7M8wr5lbyWsVPG8SYrJjA2MV8glpg5PbBHzCCBYE/+QNchr11ZSLBzI3kJxGJhG8NnK/9sM=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.7.0
Release Details
UpdatedNov. 4, 2026, 7:38 a.m.
Changelog

The AI features (VirtuProf, weekly study note, learning plan, course narrative, story narrator, pool generator) work as before, with Gemini or a local Ollama server. What changed is how consent works and how a request leaves the server. No new AI providers yet; they follow later.

Changed

  • Consent names the recipient. A learner's consent now covers the current consent text and the AI recipient the admin has configured (Gemini or a named Ollama server). If the admin switches the recipient or the consent text changes, each learner is asked once more before anything is sent; the dialog says what changed. After the upgrade every learner who used AI sees the dialog once, headed "revised".
  • One way out. Every AI request goes through a single gateway. Right before sending it checks again that AI is switched on, that the learner's consent still holds for this exact recipient and that the limits allow it. Consent revoked or recipient changed in the meantime: nothing is sent, also not by the weekly job that may already be running.
  • Real revocation. Learners can withdraw consent in their settings. It takes effect at once.
  • Limits per learner (10 requests a minute, 100 a day, 3 at the same time) and a daily total for the server, counted in the database so parallel requests cannot slip past them.

Added

  • Accessible consent dialog in all six languages, with keyboard focus kept inside the dialog and right-to-left layout for Arabic. It is the only place where consent is written.
  • Connection test for admins: sends one fixed test message to Gemini or Ollama, nothing from any learner.
  • Data export lists the AI consent and the stored chat summaries. The public privacy page names the configured recipient.

Security and privacy

  • The Gemini key is stored encrypted; the upgrade moves an existing plain-text key over.
  • Requests go only to the configured address: no redirects, no automatic retries, the address is resolved once and pinned. Link-local addresses (such as the cloud metadata address 169.254.169.254) are refused for every provider, also for an Ollama server in the local network.
  • Frontend dependencies updated for known vulnerabilities (Vue, axios, DOMPurify via @nextcloud/vue, PostCSS, nanoid, form-data, among others).
  • Prompts, answers, keys and provider error texts no longer reach nextcloud.log or the audit table. The upgrade removes such content from older audit rows.
  • Contributions of other learners are only put into a prompt when the recipient is a local server.
  • Account deletion also removes the learner's AI limit counters.

Upgrade

  • AI tasks left over from 5.5 are cleaned up; old task IDs report as expired. The upgrade stops with an error instead of continuing half done, and resumes after a crash.
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-----
SignaturehCSbN80e31JkExc32f+TDb7MdXg+K34/KfGKbWb92DBagD8pLGHsnhBwvFxTenCDEXBm2eobnLcTe6MEQaBSL+OIs41oVB1An+tdRLtnKjMVtMcXs9PA3PPQORljd+Vvh2ptodCa3Z+UTsvhiwZN4G1ESjzMVt2Ql0Po0FFzgmFVFqkPLVPGnbyzxrQZ2DNZWgukzq3DItHlRFa83i0KzXHIgKoJB9kKji9WrQWZjjIlHeCcK8ZgvAO9iYqDf+CrnkcdaWY80lny9lZj2J7oD3YOY/OBsU1BpcAl6E11jDPEHGwgHN/mGZ+WQMr4JqsXkEFxeqLDiC6zktHVCoKPAdpcOmqiUWIvAR5rfNIIA92p95rFbZWsaW2VwPahwKZryUcCHwcLIhxCKCw8M958lga0DoBxMQwsyo6n9j6DYnHJrtnfVZqlOMTv4kzJ1PrfR26o5HWN/k0TUI94H3AW5+1N9BqkIJ9zICVzQCm8oM9kXQwUJ5iafdmiKctoCCz8mwxVFUIHq0se9lpRW4bDEVSVQo1TmAIwPoRWwW1l5zgZpJM2XlBawyPNBDKILKrB7D+mTW8R6lh659E3qNEowkE88AaHIWBRipMdSh/ALxvBM5tneZC8RUOhIzaZITtLUSXB49C0AdPV4eoeuHzum5xmN8hbEAhgnY7z3a7bOpA=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.6.1
Release Details
UpdatedNov. 4, 2026, 6:34 a.m.
Changelog

Fixed

  • An exported pool lost its multiple-choice and open questions when imported again. occ learning:export-pool writes the question type as type, but occ learning:import-pool-json only read question_type. Every question without that key was treated as single choice, so multiple-choice questions (more than one correct answer) and open questions (one model answer) were rejected and skipped with a warning. The command now reads question_type, type and typ, like the pool import in the interface. Without any type, several correct answers make the question multiple choice, as the import documentation always said.
  • % and _ in a search word for course material matched too much. The keyword search behind the AI assistant's course material passed them to the database as wildcards, so a search for 50% also found 500. They are now matched as plain characters, like in every other search of the app.
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-----
SignatureqlmJFTEKJuojHnQtpGBmvWQ2Kcl46WMRiKoYOT30QA9Wye0kTDNBmuWtr6gc6MI4mK3GzRCcNd57B3+cfpRJZnyMrKG8ZtiPxwnYfnb7mfxjUk+4k86qnoqb1lwFOXhm1NOsnEBPm5Zkaa+RjpsWsJhBUXamyZCuDiHt1xVqzew/5EW4qY9dOHDsJ50F2SidRlPbwJQcb4M1aXWvrlbwShCYa/Jg+AOB6xVSb+xdMrCusYKtzCxL9cobc6/maq5fu9UYoRJwKgeQAtLiJhHAFoM4SRL5FHmrD9VnmuxYKocsH7DCpOafTdCdRq23jjsCM8CXMygQUN3k/E5DJKn+9QAn/DeJQlBfGEr0HwpSwrgXnKoUfrsk0nfLlZhOlXZb1VG3hmQIClGcyt7znbS7rswWJGJKX40ky+hl1ZMnVhq1+CjjcpOZ5SUf0bS3I3YjEpnASbhJGRYv45sAgI8ZrUX8D7lQVq0aalyKPlqdYg/OezfItOgqY5n/Uj2/7eBwuTV+VflHu2TcaWUH7H7jBsNU4XZ5q3TrYLRb1O12YA3K3W4jp49gjBKr8zlScAReJZ5jg+3CfW3ODNIMbP67776n4XyxFdh7VaxkfNSQVWjHgbRaDmwV6S4xN0KnbGj0QybfnI2ZOD8tPVFGMUc8JKxyJsD/gJSIgkWdEJpG4Ho=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.6.0
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Added

  • Scenario / info text before a question (Codeberg #10). Every question has a new optional field "Scenario / info" for the case, situation or background a learner should read before answering. It is shown above the question in training, exam and exam review, Leitner, the smart queue, the daily challenge, duel, gameshow and adventure skill checks, and in the question list of the pool. It is plain text with line breaks kept, up to 10 000 characters, and it can be translated together with the question. Because questions are shuffled in every mode, the scenario belongs to the question it introduces: for a case with several questions, give each of them the same scenario.
  • JSON import reads the scenario from scenario (also szenario, context or info), JSON export writes it, and occ learning:import-pool-json accepts scenario per question. A scenario longer than 10 000 characters is never cut off: the import skips that question and names it in the error list. CSV import and export keep their fixed columns.

Fixed

  • An existing question with two answers could not be saved. The edit form adds empty answer slots up to four, and all of them were mandatory, so the browser refused to save until they were filled or removed. Only the first two answers are mandatory now; empty slots are dropped on save as before.
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-----
SignatureQqfTt1TeDKTUved8X8Dcv5vLVqimMtZy6VYots+lshbLgmtW4pBxjKtv/Xj2FMn8rmJnIXe1wvl7S4L7kwFraAHMrFxFBhaPG0x25cRWeL/61ukwAxscqgGBp1BpViWBcXsEcS/+xWZXSRklusg0BrCdWQAMu9xmMgeXwEs1L5boFkhqQ9WPOQ+MZj4qs+1iRBwuS6eK+O46Al5kmngQtjQT/1cq59s772yP+8sX2kR1AVhViLaxqIt3dPntDE27CfaKxCBH4jOrkPqJaDmv4WH6VqAGCXM0cClJBgQKwVaOHe9FYJk2ae06zaHoXLggLWvhUAr66m4DjpcUsozd+61wZX+nhXpvgLKvgMxMwugzmw9Ju2WqV3o6qbCOaYuOBXO4kDa3E1zUKYlLav6llzfTL3jccwXwaf7D2Yb+dJsaPO30/2zFUQCE5ws+gRw0ybDDIF/uEFtC35L9VE/9ENDqhUUq5VAZ8xdJ85e3wIIhSmIz6x1e5M+8C0xeUiX3mlX1BJaWHaFSehP8h6ci1Jn52pIgcpMXWfCMOuipRTnHzBTZcIdWJ+u/zcjDF9wf5Rg/HgOCSNDtDv7wq/WK45so5TanGuhPQs7sN3V2ruECYc1O6Hu3x6PqsHEka2++1rG0jjV4Lc48V91B++OJkAKYpq9DB2/jw0HlU6aHq80=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.3
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Fixed

  • Passing a course and issuing certificates failed on fresh installs. Every compliance event is chained to the one before it, and the chain starts from a first entry that the database update was supposed to create. Nextcloud skips that part of an update when an app is installed for the first time, so on every fresh install since 5.2 the chain had no start: "course passed", issuing or revoking a certificate and completing a mandatory training ended with a server error. The first entry is now created on install and on update. Where the chain already holds entries but its start record is missing, nothing is created automatically, because a new start would hide the gap; the admin log says so instead.
  • Question translations did not work on fresh installs. The translation tables were renamed in 3.5, but the rename also ran only on updates, so fresh installs kept the old names and every translated question failed to load. The tables are now created where they are missing.

Instances that were updated from an older version, rather than installed fresh, were not affected.

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-----
Signatureo8yBMrNyZAgnKOXnfigFnYCVUX3xjMbPbI1CLqQBcIOteYVfDOdQky/G0+BtQ4u3P5UD0Fe6bBhqfJQgrzsv6fOUxDg8lUQ5XbeC3GIftD6mgGi2JR3tZcxgLxEyXdCaMyzmHiT3FotUiCqbR7uWFDp5/UAUrpgxk/8zew7px3lrH9vrfXnGNOHDWib74vIH5Sjz1HFYXJpAyuhaFUuyYvREB8+r4ZGHjG5M/RZSaYk7uQ9r2+JvDUx+6BRCG8FOg+chwkmio7C7fMcHPeCgOxoaOlYo0or0ABpRNE/E8OUryh6z288ToP9ygqTX/m2VWWsIMGo1R21D4IYgSWy2T7g2LeWlrsg8NBbCOxJMR8KuQ4hO3DP7sgEz7Aq3RDHCG0lz7b31XV1ayfIPUERC1eO33G+7n9PF1fBR0HTYd0izOiOvYQENI4NuzwWNwZiiVaawMZbUZUwxKtSca6jrcBqnbv6iYwVKrgfKIb7BSAQ+2Bg1Dja7fy48c7eY0YGn/hX4jYWS7+HprjzBf8FCxruPha4ckCeiLNOtPxzTwuxBDOY+slYHEg6Bvggv0Dx3xpoAl0D5WBpNxZjM96dMIOGelrGNLB2WLWUg0q2QA4hUQIVlBOKRjEC4uMopC1+YS8z8pAWVFIj2mV6tICTQu8VXGd6i7NMsqUKm8XTEAc0=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.2
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Security

  • The weekly study note was sent to the AI provider without consent. A background job runs once a week for everyone who studied in the last 60 days. It sent their weakest topic and the questions they got wrong to the configured AI provider to write a summary note, and it never checked whether the learner had agreed to AI processing. It now skips anyone without valid consent before reading their learning data, and the note generator refuses on its own so no other path can bypass it. This only affected instances with AI enabled.
  • Outdated consent counted as consent. The server accepted any stored consent version; only the VirtuProf chat window compared it with the current consent text. The server now requires the current version on every AI path, and the VirtuProf chat and the course summary narrative no longer use their own weaker check. The course summary narrative is also rate limited now.
  • A failed consent save was treated as consent. If saving the consent failed, VirtuProf closed the dialog and sent the waiting message anyway, although the server had no consent on record. The dialog now stays open with an error message and nothing is sent until the save succeeds.
  • Follow-up requests are checked too. Chat-memory compression and the pool generator send further requests after the first one; each now checks consent right before sending. If the server reports missing consent, VirtuProf asks again instead of showing a generic error.

A larger rework of AI consent follows in 5.6.0: consent will name the actual AI provider, the consent text will describe exactly what is sent, and everyone will be asked once to confirm the corrected text.

Fixed

  • The app icon stood out in the app menu. Learning used a coloured icon where Nextcloud expects the same monochrome glyph as every other app. The coloured logo is still used on certificates when the instance has no logo of its own.
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-----
SignatureDYtj4ULUbhWv/Own6XC4wfIshuUj8ND/dLsFst2krSvsBCs/c/lfxk81IpNAFtp5nCp7ihdBciuLw7XuQ7sfKUImWrXkE0sY2V38tD1XJWVFOpXIGBp0iDCVlxzUPFTWUuH+Ga9ItGIhUfIricBT70nkbcCN3FhTTLQqAYb89OsWHzV0DKiYLFTA3FD3ubeCahJBItQkfK0aGb7QZMKE8C3yGwAhPjFHDYXKyqiBlwbUVzjOTK7sZoWFkamh9ze9f7KwHZnvSEwUdnZmUyftTZMrJRycsPVA8cwuDNubH0Mbhw5/KMyeU7Hm65M2BJFAVm/8QPnaTSTf30RBNwi2es/DJ/S5IrRurWLVFvSXlEpAmHG82CDeN+sq6b+EF3NIjzBA8FNnfmbfG95rUYGImV9468yHSGkPkiyiMBqRj+efNImeUlhxeUdIXqMhVzB7DOFo4c3DzieKsKurmdcKCkoNCoEzh2Qybl5awSuSV9RUL5zpYVZ26FsH1hRLLu9yZOmDYHagkPFXw5aG6zazn3qSSRnikLI4kxrM3LtFXTUdlx3CxCcXczsWLkDL++nUMDxJP6p9LsbBrgple1tgGdfx5sMdKOIsJQiYF2gBJKrvcLjyji1M0PjJIJjOVpAuda/tdgcRY+1EYzkdcvxogW4Mh3Me45cLUK/EVYbIQ1k=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.1
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Fixed

  • Tool selections were saved but never applied. Reported again on #7 after 5.4.6: "regardless of the selection, all tools remain visible". 5.4.6 fixed the settings form, but nothing read the setting where tools actually appear. The course's learning space ignored the admin's global selection entirely, and an empty course selection ("every tool off") was read as "no selection" and showed all eight. Both levels now apply: the admin's selection, narrowed by the course's. With nothing left, the Tools tab disappears, and the dashboard drops its tools link when the admin has switched every tool off. After saving in the course administration the learning space updates without a reload. The unit tests for the learning space had carried their own copy of the old logic instead of calling the component, which is why they stayed green; they now test the component, and a rendering test counts the tools a learner sees. Verified in a browser on Nextcloud 33.0.9 with MariaDB 10.11, before and after.
  • The pool rules dialog showed every pool as required. The server sends the flag as 0/1, and the dialog read 0 as "required", so the checkbox was ticked for supplementary pools too — and saving any other rule in that dialog marked the pool required again. Found while adding the option below, which depends on that flag being right.

Added

  • Practice exams from required pools only. Follow-up on #9: a course may hold supplementary pools for self-study that should stay out of the exam simulation. The practice exam settings have a new option, Use required pools only, which draws questions only from pools marked Required in the pool rules. If the option is on but no pool is marked, instructors see a warning in the settings and learners get a clear message instead of "no questions". Tested on a fresh install and on upgrades from 5.5.0, on MariaDB 10.11 and 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-----
SignatureuJxsLjiKbsbrXM7v7CAn0FEqCEubH+3VTYxJgBTLm0Z4TxBY3sgcSV9+dDeV24fiHYL18f4tzB+013jku9suSZ67tZs+0cvzzhP77wy2LcRrV4VI6yFFOtmgS2HU+bc07Lrv/nIQz0HtcLfZjbenU1FyBRSUpWFGysQ8vuEabKS8m2Jneto113Qcty0YOWGqGoBI/MoQiaRlqM8XcF4kRodtPNmHxlGXUqJP/hO3tZP7WNIVBeDNz4p7TlpPEfjinuHniO2Gc6tmUK4b1B9BiCFzgXbxgV5ylzDgrbwbiqb2diqPezQFUaoIzcPshhR9602h1sL3BWi4McyvvDuEccXIxFLlvZYlH/O85yepBuzyKNsXquId/axgnKOQUJYWL0jPnat/yyUa+7MSRGVsYYro8xrVG4wGZD2sxXzDtolNd9F/gsg+4OR2lU5hUKOfBTuOp+cwQ/EXwX3wcyc7QszOvH8EDN0g24gC9dlB5Cs0ge6cybEx92RoWkE7bHf4CTe5FBhvSu+eRg1CTky1QS/x8NNsLbT4fDJvoID3QSwo+sj8Z9jUMN0gCT1qhu0iiXv3g0hQmnOcZhFwnmjoVNIw6HxNZlm+SZ4ceWjR4g/QZcPgmb7i0uVwR1gkBqJ3HOwc2fMh9VoEFqZ93SvRbTbB4t/eeSok7PEip8khvgY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.5.0
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Requested on #9 by a team preparing aviation radio courses: until now the exam mode assumed an IT certification.

Added

  • Practice exams per course. Requested on #9 for training outside IT certification: the exam mode only knew the fixed CompTIA presets (90 questions/90 minutes and 45/45, pass at 720/900). Instructors can now offer a practice exam in a course's Administration tab and set the number of questions, a time limit or none, and the pass mark in percent. Learners start it themselves from its own tab — no pool to pick first — and every attempt draws a fresh random set from all of the course's pools. Afterwards they get a review with the explanations. The CompTIA presets stay as they were; the practice exam tab shows even where the preset exam is switched off.

Practice exams never count towards a certificate: they are marked separately, and the pass evaluation reads only the other exams. For the same reason they are left out of the locks that keep an exam from being used as an answer key — an untimed one left open would otherwise block training and explanations on its pools indefinitely. The reverse direction is guarded: while a certificate-relevant exam runs in the course, a practice exam cannot start, and an old one withholds its review.

The pass verdict is decided on the server against the threshold stored when the attempt began, in integer arithmetic (74.6 % does not pass 75 %), so a later change by the instructor does not rewrite past attempts. Tested on fresh installs and on upgrades from 5.4.6, on MariaDB 10.11 and 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-----
SignatureQkyHXEoC5fAsNey6cnMRGCQpGB28CVQZ/54fNM1YfcAZEzfCZD8MoBlqlqq0ZhQli+CIHm+Te6xRdvA1lCrcnLODGjejcTgq/6+iXgcuSvJgd7Ev7IDm2ltRGUyq6j9DD3u85EfCvI01h37mIAWLYYsLBUP2C54qt9jU0rgyS0n0E1cp6r0+uGtd0ty/dHQwAycEmvlhAmyN9eOQXphbMK7sIItgBCnW0ykXEdxgKhCkpgcmMqxpSyrd1Ub4/lcLVoQ2JPxWun7/E9JVhPZ6XgpaRrrHlsAx0iQ/KBoKnT33jmGntcaz72vXBQZt5qWH3qkZoxinfpJvUDLE4VjvzMPyIHFqOBIZqA+HvCYMUnEpKAEuLy9AEwewLEnXqGYAH+CqpAojOtjaPHtWzd0oI8+CFEYt8VTfZS5ZD+ueMbYl/ARtq91MozvwjggM1UHfzeTybtnlJC77yYMxeqqQZtSupAS/GUboSIG3wu3PfD+x1X7PW5rJo+m3lR7vJ4Oz8SEIPNlt/s3jX+5lainSUAtOfYp30fCR0IcmkzmbajL+m7oBIqWQdSvxFqTUOr2+SmOmVERpXTikDC4X0XXe4cM1pbOiGolQqhc5RI2FC6c7aYcOT/rple7DLkKiT/I76LyqT7c/SE0NpJXv5N3DDAmuiAMti+PlreU3TiuTmSU=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.6
Release Details
UpdatedOct. 15, 2026, 8:26 a.m.
Changelog

Both reports came from the same install on Hetzner Storage Share: no shell, no occ, and MariaDB. #7 found a course view that failed and tool selectors that never showed their state; #8 found a false audit alarm and a certificate setup that required a command the host does not offer.

Added

  • Certificates can be activated from the admin page. Signed certificates need an issuer signing key, and until now the only way to create it was occ learning:cert:init-issuer — unreachable on managed hosting without shell access, where certificates could therefore never be enabled. Reported on #8 from Hetzner Storage Share. The certificates notice now has a Generate signing key button.

It only ever creates the first key: if one exists it refuses with 409, because an existing key may already have signed certificates. Rotation stays occ-only. Creation runs under an exclusive lock, so a double-click cannot leave two active keys — a guard the occ path now shares.

Fixed

  • "Checkpoint overdue" on installs with nothing to checkpoint. Reported on #8: the warning appeared next to "No checkpoint yet" and "Events since last checkpoint: 0". Overdue was measured from the last checkpoint, and a missing one counts as timestamp 0, so every fresh install was overdue from the start. The same false alarm followed every quiet week, because the weekly job deliberately skips when no new audit events exist. Overdue now means an audit event has waited more than 8 days for a checkpoint — which still catches a job that is not running.

The warning also pointed to occ learning:audit:verify, which checks the chain but creates no checkpoint. It now names the actual cause: a missing signing key (checkpoints are signed with it), or background jobs that are not running. - Tool selections appeared not to save. The selectors bound :model-value on native <input type="checkbox"> elements, which have no such prop — Vue rendered it as an inert attribute, so the boxes never showed the stored state. Every tool looked unchecked, clicking an already-enabled one changed nothing, and saving wrote all eight back. Reported on #7; four occurrences fixed. - Fresh installs never received three course tables. Version009900 does its work in postSchemaChange(), but a fresh install executes only changeSchema() and then records every migration as done — so it never ran and could never be revisited. Version010100 creates the tables in changeSchema() and copies any legacy rows afterwards. This is the likely reason a course view failed on the reporter's install, and why reinstalling the app reproduced the problem instead of fixing it. - The certification toggle always saved the previous value. It listened for @update:checked, which @nextcloud/vue 9 components no longer emit. - The active exam slot never displayed. The view read res.data.slot; the controller returns exam_slot. - Two buttons missed by 5.4.5. Both passed a styling value through a dynamic :type binding, which the new guard did not yet check. It now covers dynamic bindings too. - A missing database table no longer takes down an entire course view. Reported on #7 from an install on managed hosting, where oc_learning_course_curriculum_scopes did not exist. That table comes from a rename migration (Version009900) which deliberately aborts rather than creating an empty table over existing data — so an install can legitimately end up without it, and the app treated that as fatal. Curriculum scoping, paused-question overrides and announcements now fall back to "nothing configured" and log a warning naming the migration and the occ command that repairs it.

Writes are deliberately left throwing: reporting success for a save that stored nothing is the worse failure, and the caller has to be able to tell the user it failed.

The "is this table missing" check moved into Db\DbErrors rather than being copied — UninstallCommand has needed the same predicate since v5.3.0. It stays narrow on purpose: anything not positively identified as a missing table is rethrown, so a genuine database outage cannot turn into silently empty results.

This limits the damage. Why the table was absent is the "three course tables" entry above.

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-----
SignatureZ4KteEBRWfaEdZUSXUPV5e8sHFHO72aLZKRIFgUDMXjKJn97R3QodN3djq9hJLgcT0RUHxgZvfL4SeG2RvVYeCLOCIxcsGv6QV+yPoXw6w15Xay8CXBUExRuYRLKik+txLT0ttekRCHiTPrJU737R8CeodH0/dbItL6IJ/5JEOQv5iK5/8MLjL2wtJqBm2VeaE4H5nK/RYCvG/DNOTiFJgGgbhJlNhZcz+Yzg8LVyCCbRtQkU8MqagLTTAHbHCQU2+0h+0ommbO+Lcx6aHpvBKAic+VOB89YbbLoQpwtf5tirhjAM0FxT4EFK1OYTV1FfmRIz6wN1oIixcnPYSF6izmidK3jtTM+tBC6CqTUZ7lKtu+E+t7vWX+QrR2JEhVEi4FQ3bq/JoMUyGgnYxGvwB8p63T9SJeK0FEaI4fwgmHtA18xtjPUkUywE7CAp8DtVZ6Bgr163z1FUO8XOSsbPwUgh81VkIH/8NFHnReL5sBBDWBwFgsZ6iyMStv81OTZGglJBFyn9RjlV5HhrtprVLsDEr+G9BW9JOa/uM/dHFvuBxHGWdGadZ8QBlapI4ZD5Tz8GaWvPlvFYqeaovisViEcLN0TTSVN0+V95Cl2CZYb/4+fUKGPEhcTT5vEsb+mPYk/gCnz+NmqcSFp2ezR+GO2FWbx4EBSnbnlNFI8SEk=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.5
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported in #7 by an external user on macOS, reproducible in both Safari and Chrome. The report was precise and its diagnosis correct — it just described one button out of 244.

Fixed

  • "Upload image" saved the question before a file was picked. The Vue 3 migration (380dd9a, 2026-04-03) moved the app from @nextcloud/vue 8 to 9, which swapped the meaning of two NcButton props: styling moved from type to variant, and type became the native <button type> attribute. native-type was dropped entirely. The v8 spelling was never migrated, so every button passed a styling value such as "secondary" to the native attribute — and HTML's invalid-value-default turns an unknown button type into submit. Inside a <form>, every one of those buttons submitted.
  • "Cancel" in the question editor saved the question instead of discarding it. Same cause (type="tertiary"), not in the report, arguably worse than the reported symptom.
  • The three save buttons only worked by accident. They carried type="primary" plus a native-type="submit" that v9 ignores; they submitted because the styling value was invalid, not because they asked to. They now say type="submit" explicitly.
  • All 244 NcButton instances were migrated to variant (the 222 outside forms were a visual-only defect: with variant never set, every button in the app had been rendering in the default secondary style for 5.5 months).

Added

  • tests/unit/NcButtonTypeProp.test.js — pins the v9 prop contract against a real NcButton render, and scans every .vue file so the v8 spelling cannot return. The four existing gates could not see this class of defect: it is valid Vue, valid JS and valid HTML.
  • tests/unit/QuestionFormSubmit.test.js — mounts the real form with real buttons and clicks them, asserting which ones reach a submit. Reverting the fix turns it red, on the submit count rather than on an attribute. Note the precondition the report already implied with "open an existing question for editing": while the required fields are empty, constraint validation suppresses submission on its own and hides the defect entirely.
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-----
SignatureX4k7LjqPFPx+SZ0KlaAr4FFFC9YdnYlt3mHWOnNskXRJI6+a7n2zgNlf6CwGnr09A8vJChj3uPg7y4MgnPZ4HLmtaNRPP05ol57+ev/o/2CZGElat0qGLwCHXE7/q71BVrcvzGXJOmc3B79R9iXjIFj+JR4qhWzZ1v0li3Fshz1mPwX2oQeAQ/7w5O1ffG5Vchk5hfJpU+X2phfuyBcxX3YpoUqNWOP6GAot8ENkl3znWoDidHICihBC1Szk25BWek0aUQng/ROo6zYn9xt8/d8xfIcJ0dBwvO7h29jeRDTxcRKFd4eBaXFnvesL58KZEC3vLSYduYzzK3ENccb8IXc0E8cqz0WXzI9V4l2cN5VaEYYpLO4I5++lwWqLPeHLAL6Gc543ihiDh04JgTuRA5VtrMGg6TCUpkmkWcdfKPEwuPWK+xq+cXBiHT9LyTqGsD6rfagyLGE2tnwnjni9++CIwj6ScsDU91CBFf/bZjDs21YouNXSpkkYsGFTuF4a3VTqFE1PuDcvZt02/1T+HX5Pwlj6BNm20O/81R2nuqtCU5kBgUbTLqtHimyufr0BKBWXclHZZEtKBOV/+iI6llWiSF6wDH/btNcgO9XlnGP3nK0S9x4GTTIXWqlsCZPILWByGYW47l6X7Fxb5fTgyZQMLz1kyV8om7Qs0q5+f34=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.4
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported in #6 by the administrator piloting Learning in Vinnytsia. Two of the three questions turned out to describe real defects rather than gaps in understanding.

Fixed

  • The assistant rendered German text inside a Ukrainian interface. VirtuProf does not translate through Nextcloud's t(); it resolves against its own bundled catalogue, and 161 of its 343 reachable strings had drifted out of that catalogue — including every FAQ answer body. translateVirtuProf() then fell back to the raw source string, which is German for most of them. 63 of the 161 were already translated in l10n/uk.json and simply never made it into the catalogue; the remaining 105 are new and now exist in all six languages.
  • The AI assistant was told to answer Ukrainian and French users in English. The system prompt's language map only knew German, English, Russian and Arabic, so anything else produced the instruction "If unsure, default to English". It now covers all six languages.
  • The assistant ignored the Nextcloud interface language. It derived the response language from the opt-in content-language setting alone, with a hard English fallback. It now falls back to the user's Nextcloud interface language, then the instance default.
  • Choosing Arabic as content language did nothing. The dropdown has offered it for months and the request sent it, but the server's whitelist did not include ar, so the value was silently replaced with the empty string and the request still answered 200.
  • "Create summary" wrote an English note for a Ukrainian user. NoteGeneratorService, which the assistant reaches through its file-intent path, carried its own copy of both the language lookup and the language-name map — and its copy was missing French and Ukrainian in exactly the same way. Both now come from one shared resolver.
  • The assistant panel header rendered three empty strings. Its translate helper passed the key where the function expects the language, so the panel label was blank and both the minimise and close buttons had an empty accessible name — in every language, German included.

Added

  • An instructor group setting in the administration page. Members of that Nextcloud group may create courses and question pools without Nextcloud administrator rights. The mechanism is not new — it has been learning-instructors all along — but nothing named it: the admin page referred to "the configured instructor group" without saying which, and the manual claimed the instructor role was per-course only, which is wrong for creating a course in the first place.
  • The five textbook fields in the pool dialog now explain themselves. They are optional and only pay off when a pool mirrors one chapter of a book; the dialog says so, and each field describes what belongs in it.
  • A fifth i18n gate (scripts/gen-virtuprof-strings.mjs --check). The VirtuProf catalogue is now generated from l10n/*.json rather than hand-maintained, and the gate fails when the two disagree or when a reachable assistant string exists in no language file. Verified: removing a VirtuProf key from all six language files leaves gates 1–4 green and turns this one red. The catalogue grew from 252 to 429 keys and lost none — keys reached through vt(<expression>), which no literal scan can resolve, are carried forward until they leave l10n/de.json. --list-indirect names the 22 call sites this protects.

Documentation

  • New manual section 2.3 on letting teachers create courses and pools, a corrected role description in section 1, a field-by-field table with a worked example for the pool dialog in 3.1, and two new troubleshooting entries.

Internal

  • deploy-prod.sh --stage-gate now copies appinfo/ into the analysis copy. Without it ClassResolutionTest — the regression test written after the AIController/AiController outage — errored out instead of running.
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-----
SignatureDLyhhVBf9XRwBNeoyTKog1TtpFna/YIjmDn+ZjlT9XdOHDkP+/lCeH5/llEg9T1YqKUzejWB6zRtksExb7/DCODs5W8r2AiSVkO1moPld7QVcjHOQXWZ7lXKqQ8FIDPKy6Xfa6QNbGc3HE1J0SGMfW315PV07TL074azidYLszcQzuVsoPcIir/FLptx8pdLReN2G//GucWdoGeAR1djjn4YfdFlCAE0eDuJnqi3dChkin0PMYFTbdK4uLqz6ocEkHcpjaw+0JbSe/7FZcoAkmEEHiV9rBbj9uikbBj/k1wYV8h4OD9TGJpK595NQ56GC4Py7FaCTD2mMLvjDiMsMYVq63hffv5xzwn0RPmAwosGImUpapd3ft5VTHuY+yP5prrrtNadF9Mm45KA0mPKrIJHOwey8xqyPfMl7N0RlhWBWwKbae436EQgTLifLdo4A/nz2gR/Fix58LLyQoHAKGKE/wdQ5jDrI5Dv7xA4cBzgcPeOC5S9vxUuAuKXuTQemliU9065EUw9nv+rTdTypx7CIzgVHtnjlXIapAQilENk1uLrg2b8AZD4PVXLq6UW1VLyO/H02XYKRuw67R4ZMbqg0tszrhamlCiGJB7WbvWbxP9NTfI0cFHZkq0dlRCUcUgIlQYj772fKw5t/cTHIJky8l9sBG/ObqWJVEawpC4=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.3
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Found by clicking through the 5.4.2 deployment: the wizard's "Skip" button sat at top: 16px, directly underneath the Nextcloud header, whose avatar button swallowed every click. The splash screen carries its own skip link at the bottom, so the first step looked fine — but from step two onwards there was no reachable way out of the introduction at all.

This is not new in 5.4.2; it has been there as long as the redesigned wizard. It is the sharper half of what #5 describes: users could not get rid of the introduction even when they tried.

Fixed

  • The wizard's skip button clears the Nextcloud header and can actually be clicked from every step. Verified in the browser: a click on the button's own centre used to be delivered to the header's avatar.

Added

  • Two browser tests that would have caught this: one walks a new account through the introduction and asserts it stays gone after clearing site data, the other asserts the skip button — not the header — receives a click aimed at it.
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-----
SignaturetBYdgRNTvcdREMCynplVM4OPCgWQck7GP+w3btJG710KKvOl1+YwKqBTcmUk3HoCwy+ws6BVOljRjCfJumhvCzPIIFpjs4fEsLskZyp7hueeRx9NTxCDe1fcWuZAA593HD1aDmrURuBWULQ09DRRcngYm3eD8X1yyr1oBvcwtFHkm+zMDOru484j4/Etl/R87I4vcA25ynoI1Rt8yxso52ZR3PFOb8Y+N4ghd7YlP01uffQAUlPEwQCrXwLXED7x6Z+ocSVcHVrmMFWx4f6ElksbV3UlIHd3ewB72t/kWNqCRqSTIf9L6DbyTb5MKFWwgBXRaP4vRbzZCrpukutZ5FprnR8XQ3KWNXfxJ4yW6nNYiZRYPuWu+sBKChWlDBa9RMgYnPv+ePV+H2EgW8NvI/DAXttcZDTsoFwZ0q/epmCuprdmcBBRefB3ysufGzzN7jQeVBEBr7dEkOtfadXpaLsLk03oQEu+1lM6Re7yPxYJukzWY3oG8tZXu92Q4TSnIAaSTAXP2+p/3wOEnm2u5r8dOnDQv3jUsSuJaGBBGRpIeTx1RAv2cWI698IAw3QJz1BQwnXpKdQei14hIRwacnB5mpJzj/N9cRmiGhQFrPaiNM2pcXoQGpLS73sa1p04Pl4bBrHFFqL3HzFqyKVGqhpH8kBMtR/NH1xcMebNYXY=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.2
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported by the same administrator piloting Learning in Vinnytsia (#5): the help wizard reappeared for users who had already been through it, and its introductory questions ("How many bits are in one byte?") were beside the point for staff in occupational safety or HR.

The reappearance turned out to be three separate defects, not a missing setting.

Fixed

  • The assistant's introduction never honoured "no thanks". It read that setting from a response which does not contain it — /api/profile/telos/status returns the completion flag and nothing else — so the check was always false and the introduction started again on every single page load. The setting had been saved correctly the whole time.
  • Whether the wizard had been seen lived in browser storage alone. That is scoped to one browser on one machine, so a second computer, a different browser, a private window or cleared site data all brought it back. The server is authoritative now; browser storage remains only as an offline fallback.
  • Skipping the wizard recorded nothing on the server. It counts as a decision now, like completing it.
  • Instructors were shown two introductions in a row — the wizard, and the slide tour the moment it closed. One introduction, one decision.
  • Existing users are marked as done during the upgrade, so the update itself does not send a whole instance back through the introduction.

Changed

  • Instructors now see the full nine-slide tour inside the wizard. It used to follow the wizard as a second, separate introduction; folding it in keeps the content while removing the duplication. OnboardingIntro is unreachable for now and becomes the voluntary tour in 5.5.0.
  • Declining the assistant's introduction now puts the assistant in its reduced mode instead of leaving it in an undetermined state.

The introductory content itself is being reworked for 5.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-----
SignatureRWPqQbdfMQs4sWt1fQY2J12frNlvsBfyyxZVm5GD+QHIFuCDmqmhsunLgzv2zfzWn08hoinqIAE7hyOYf42Z38K17/vpiYxSnsltjK5f1gP1gomdeKwgGrTRpduOfDqXbH6KOGx82opBm1cjV6Whi2FVJrwCNE/MFwOzhdwshYHMWlOxVnCUDvm2iHfveakQ2fKbC7BAR3Sm8YtD/eUqsz/hNiGOZcCY71xo+LpUJge5bY18D9kz8GpXiwLhf8Ivzb5Dfj1b1xrwIS6WO19i1tgLpJ7adJXftyUv++o2DaPRLw1O3IcV0UUmmgo9GeDaq/1ZCtav5lu0r1O8crLQ1uSqZmwyzIkd/UG5feTsLC3ttF5/DA9nBhup7Gd6nIwluVKjaMUfyfmcypFS428rtlNefyJjgkD5gOi0aZH0GVE4cnEEkh3bzebPTLXHoTVjZ6SJIjYxQDHfRHJxObO4GCFuc/Fnq+MIGEySLlubt9q7Ja0GyphBmO1Vw3Z0fiCT24Ny6PrIpknjQcuUvb7/la6txUzB0e+IWGLm5YJf6FMZMgBSt4ZKOtUwONCrZ3RDHYIYLhtunfE1wCJNRt0ammhpTjUz99bjmQABkYfAeTQvznmZBzTi7PoEDyuHLm5sAPByjNhpySHDfe4nrLfArK30dmVoyqJgoBVve0+b79c=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.1
Release Details
UpdatedOct. 15, 2026, 8:25 a.m.
Changelog

Reported by the same administrator piloting Learning in Vinnytsia (#4): one error and one warning in the Nextcloud log. The warning was what it looked like. The error was the visible edge of a defect that had been silently discarding onboarding results since v4.2.0 (4 April 2026) — seventeen releases.

Fixed

  • The onboarding wizard could not save a profile, and lost two more things while failing. OnboardingRedesign.vue built its own request body instead of using the shared payload builder, and posted '' for the list fields where every other caller posts an array. TelosController::saveTelos() declared those parameters ?array, so PHP raised a TypeError inside Nextcloud's Dispatcher — before the controller body ran, which is why the controller's own try/catch never saw it and the user got a bare 500.

The damage was three times the reported symptom, because finalize() ran three steps inside one try with an empty catch: 1. the learning profile was never stored — onboarding_completed stayed false, so VirtuProf kept asking for a profile the user had already filled in; 2. the AI consent was never stored, even for users who explicitly opted in. Since the universal consent gate in v5.2.1, that locks every AI feature behind consent_required for them; 3. the selected starter pool was never imported — the wizard offers a pool, the user picks one, and nothing arrives.

None of it was logged. Three fixes, at three different levels: - OnboardingRedesign.vue now builds its payload with createTelosForm() / buildTelosPayload() — the same util PersonalSettings.vue and VirtuProf.vue use, so there is one payload shape in the app instead of two that can drift apart again. - finalize() settles each step independently and logs a failure to the console instead of swallowing it, so one failing step can never cancel the other two. - saveTelos() / updateTelos() accept array|string|null for the list fields and split a string on the same separators as the frontend (, ; newline). A malformed telos payload now answers 400 instead of raising an uncatchable dispatcher TypeError.

No migration is needed and no data was corrupted — nothing was written, rather than something wrong being written. Affected users recover on their own: the profile can be filled in under Settings → Personal or through VirtuProf, the consent dialog reappears when an AI feature is used, and a starter pool can be imported from the pool list.

  • Activity\Provider::parse() threw the deprecated \InvalidArgumentException. Nextcloud replaced that contract with \OCP\Activity\Exceptions\UnknownActivityException in NC 30; until then every activity-stream load logged a deprecation warning, and from NC 39 it will be logged as an error. Both throw sites migrated.

Notification\Notifier had already been moved to UnknownNotificationException — the same NC 30 cohort — and this provider was simply missed. Each throw site now has a test, so the pair cannot drift apart again.

Tests

  • tests/Unit/Activity/ProviderTest.php (new), tests/unit/onboardingTelosPayload.test.js (new, covering both the payload shape and the step isolation in finalize()), and seven cases added to tests/Unit/Controller/TelosControllerTest.php.
  • Every one of them was verified to fail against the pre-fix code before being kept — the controller cases reproduce the reporter's exact TypeError, and the finalize() case fails with expected [ 'telos' ] to deeply equal [ 'telos', 'consent', 'pool' ].
  • tests/Support/PhpUnitStubs.php gained OCP\Activity\IEvent, IProvider and UnknownActivityException; the PHPUnit bootstrap deliberately does not load Composer's autoloader, so OCP types live there.

Also fixed — found by auditing for the same bug class

Codeberg #4 was one instance of a pattern: a frontend that posts a body the controller does not read. Nextcloud's dispatcher does not reject unknown keys and fills the missing parameter with its default, so the request succeeds and the app quietly does the wrong thing. PHPStan cannot see across the HTTP boundary and service tests start after it, so nothing caught these. An adversarial audit of every such call site turned up four more.

  • Saving a course schedule deleted it. CourseController::setSchedule() takes entries; the curriculum tab sent schedule. $entries stayed at its [] default, and ScheduleService reads that as "replace the schedule with nothing" — it deleted every row, inserted none, and answered 200, which the UI reported as saved. An instructor editing a curriculum schedule lost it, with a success message.
  • Reported questions lost their identity. SupportTicketController::create() reads questionId/poolId/courseId out of context; VirtuProf's "report this question" sent them at the top level, so the ticket's structured columns were stored as NULL and the question was identifiable only through the subject line. The same component already built the correct shape for its other ticket call.
  • "Explain the question I just got wrong" did not load that question. The chat request sent questionId; the controller declares lastWrongQuestionId. The value was dropped, so the retrieval context never included it. The richer questionContext usually masked the defect.
  • Two icon URLs were relative where Nextcloud requires absolute — Activity\Provider::setIcon() and Dashboard\LearningWidget::getIconUrl() (whose interface says so explicitly). Desktop and mobile clients do not share the web root, so those icons did not render there. Notification\Notifier had already been doing this correctly.

Each of these is now pinned by a request-boundary test in tests/unit/requestPayloadContracts.test.js, verified to fail against the pre-fix components.

Also fixed — in the profile endpoint itself

  • An emptied help-topics list was silently preserved. saveTelos() forwarded the list only when it was non-empty, so [] and '' both meant "omit" rather than "clear". Emptying the textarea and saving kept the old value — and since help topics are published to classmates through the classbook at course or public visibility, a user could not withdraw a topic once shared. An omitted field (null) still means "leave as is"; a supplied empty one now clears, matching what updateTelos() already did.
  • A malformed updateTelos() payload could half-save. Skipping a non-array telos and carrying on meant {"telos": "oops", "bio": "changed"} stored the bio and answered 200 — a worse outcome than the TypeError it replaced. It is now rejected before any other field is touched.

Known limitations

  • List values are not round-trip safe: a topic containing a comma, semicolon or newline is split on reload, because the frontend has always joined and split on those characters and there is no escaping scheme. The string path added above follows the same convention rather than introducing a second one.
  • Users whose onboarding failed before this release keep a local "onboarding seen" flag while the server has no profile. They can fill the profile at any time, and the assistant prompts students who have it enabled, but there is no automatic reconciliation for instructors or for users with the assistant switched off.
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-----
SignatureD3aST42eiKf/nYaI5/U8YFgvDZ7RMjOBDOb+wZpO4QpWxrslmpBK86vnPCTj2glzExY08m104traCr2c59xfe/MYRWc3xfVgeBgyLQa0/KTJUO2ecJzP0pwMb24ybNYB5vSKq7ONgZPvv/9WNoo+BKPzos3CT2xpO0+0P0oKGXyErMCU2kJnG7kfTr1ih2jzurKy3vEXGsj43Hth/skA+DNRToPZH6fMyjBYkhaK0ezVO4+Sd18jImh7kAdEl1uX429jC70cQCrurkT8+4KPax0QCrOSza7Va7qHAnM7NuqHl+Yn0lNQwvUSTlnP3dpkUVOHg/e2REyROD76NiPcv7RX1vQH1Yli8eQ6MOfg2wM7gq9sCcJ9yG83VJ9wR+CZqDHbBYe7hm09p70J1Hrx3p1vDmYVhl8CapA1H1tZz58YkoTwbycwbesy6xbtM+nG7m20AjE09s9Ye3Sg+vWWu7bQCbKEZCt4OXxEHwQWXX6VQ0toFNDm8ZIxFhnyMG4C7Z/kIC51h5wASBCE+ZNyTmhDZogBEfbJrdxu+Zq8JIPq++s1IBUUHUFUvBbwP5iydFF2Ftcu4EzbxvwfpqKJNiAMbCJFOUMPYFAXL4ToSLPzqZDtVZpzki9b44wtyJ0io74iu4U1ohgD/8erbwz21qXnzasdTwxtyRPNfBejhLo=
Signature digestsha512
Dependencies
Required Nextcloud versions >=33.0.0,<36.0.0
Minimum Integer bits32
PHP>=8.1.0
Learning 5.4.0
Release Details
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 a.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
UpdatedOct. 15, 2026, 8:25 a.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_announcements → learning_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
UpdatedOct. 15, 2026, 8:25 a.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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:24 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:24 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:25 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
UpdatedOct. 15, 2026, 8:24 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