Show quiz exhibits, keep sign-ins alive, and close a batch of edge cases
- A quiz question can now carry the diagram it refers to: a Markdown image line anywhere inside the question block is parsed out of the text and rendered under it (referrerpolicy="no-referrer", lazy). The URL points at the course provider, so an offline install gets a note and a link instead of an empty box. - New optional SESSION_LIFETIME (30 days in the example config) replaces the browser-session cookie plus php.ini's 24-minute idle limit, which signed people out over a long pause. Configs that predate it behave as before. Behind a proxy trusted enough for CLIENT_IP_HEADER, the cookie is also marked Secure from X-Forwarded-Proto — TLS ending at the proxy no longer costs the flag. - Error responses no longer echo the exception message unless DEBUG: a PDOException names the DB user, host and tables, and that handler also runs for visitors who never signed in. - The .htaccess denials are anchored to a path segment rather than the root, so videos/ and includes/ stay unreachable under a prefix such as /coursehub/ instead of silently opening up. - A rescan takes a MySQL advisory lock; a second one answers "already running" rather than racing the first and flagging its rows missing. A file swapped in at the same path with a different size drops its cached duration so the probe reads the new length. - stream.php discards output buffering and zlib compression (both break the promised Content-Length) and answers HEAD with headers alone. - A progress save with no duration keeps the length already stored instead of nulling it, and the folder view builds its rollup once. - In the SPA, a view whose data arrives after the route moved on is discarded — previously a slow load could paint over a newer screen or start a heartbeat for a video nobody was watching. Also: the quiz submit button can't be double-fired, leaving a video during the autoplay countdown no longer jumps ahead later, the final position is always saved on 'ended', the playlist scrolls the current entry into its own box, the login field takes focus, views start at the top, and a stray "%" in the hash no longer throws. - Dropped the unused requireLogin(); registerUser() answers "taken" instead of a 500 when two people race for the same name. Claude-Session: https://claude.ai/code/session_01AXMrtWNUV7NU1pCUHBG4t1
This commit is contained in:
@@ -76,6 +76,12 @@ Top-level folders become courses on the home page.
|
||||
the web server / PHP user).
|
||||
- DB credentials (change the password in production).
|
||||
- `ALLOW_REGISTRATION`: set `false` once your users have accounts.
|
||||
- `SESSION_LIFETIME`: seconds a sign-in lasts (the example config says 30
|
||||
days). Unset or `0` means a browser-session cookie that also dies after
|
||||
PHP's idle limit — 24 minutes by default, which signs people out over a
|
||||
long pause. Installs whose `config.php` predates the setting keep that
|
||||
old behaviour. If other PHP apps share the server's session directory,
|
||||
give this app its own `session.save_path` in the vhost / FPM pool.
|
||||
- `DEBUG`: include the exception type, `file:line` and stack trace in API
|
||||
error responses. Errors go to the PHP error log either way, so leave this
|
||||
`false` on anything publicly reachable. Installs whose `config.php`
|
||||
@@ -122,6 +128,19 @@ Questions start with `[&]`, options with `- `, and correct options end with
|
||||
`-- Correct`. Questions with more than one correct option automatically render
|
||||
as checkboxes instead of radios.
|
||||
|
||||
A question that refers to a diagram carries it as a Markdown image on its own
|
||||
line, anywhere between the `[&]` line and the next question:
|
||||
|
||||
```
|
||||
[&] Based on the diagram shown, which of the following are true?
|
||||

|
||||
- PE1 imposes a label -- Correct
|
||||
```
|
||||
|
||||
The image is shown under the question text. It is fetched by the browser from
|
||||
wherever the URL points (usually the course provider's server), so an install
|
||||
with no internet access sees a note and a link instead of the picture.
|
||||
|
||||
Quizzes are graded server-side (answers are never sent to the browser before
|
||||
submission). A question counts only if the selected set exactly matches the
|
||||
correct set. Best score, last score, and attempt count are stored per user;
|
||||
@@ -260,8 +279,13 @@ returned as JSON, with detail gated behind `DEBUG`.
|
||||
can be spoofed to dodge the per-IP limit.
|
||||
- The shipped `.htaccess` (Apache) denies direct access to `videos/`,
|
||||
`includes/`, `config.php` and `schema.sql` — without it, video URLs would
|
||||
bypass login entirely. Nginx setups need the equivalent `location` blocks,
|
||||
or keep the videos and includes outside the docroot.
|
||||
bypass login entirely. The rules match those folder names anywhere in the
|
||||
URL path, so they hold whether the app lives at `/` or under a prefix such as
|
||||
`/coursehub/`. Nginx setups need the equivalent `location` blocks, or keep
|
||||
the videos and includes outside the docroot (the safest choice either way).
|
||||
- Only `.mp4` files ever reach the browser, and only through `stream.php`.
|
||||
A quiz's exhibit image is the one thing loaded from elsewhere — its URL is
|
||||
whatever the quiz file says, sent with `referrerpolicy="no-referrer"`.
|
||||
|
||||
## Production notes
|
||||
|
||||
@@ -269,4 +293,9 @@ returned as JSON, with detail gated behind `DEBUG`.
|
||||
a handful of users. For heavier use, serve files with `X-Accel-Redirect`
|
||||
(Nginx) or `X-Sendfile` (Apache) instead of streaming through PHP.
|
||||
- Put the app behind HTTPS — the session cookie is then marked `Secure` on its
|
||||
own.
|
||||
own. When TLS ends at a proxy that talks plain HTTP to the origin, the
|
||||
cookie is still marked `Secure` provided `CLIENT_IP_HEADER` is set: the same
|
||||
trust that makes the proxy's client-IP header believable makes its
|
||||
`X-Forwarded-Proto` believable.
|
||||
- A rescan is one-at-a-time: a second **Scan library** while one is running
|
||||
(from any user) answers "already running" instead of racing it.
|
||||
|
||||
Reference in New Issue
Block a user