Commit Graph

7 Commits

Author SHA1 Message Date
Harwinder Sekhon 6a4c972c47 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
2026-09-06 12:31:21 -04:00
Harwinder Sekhon 66e4905796 Let the login throttle see real client IPs behind Cloudflare
Behind a proxy REMOTE_ADDR is the proxy, so the per-IP limit would lump
every visitor together. New optional CLIENT_IP_HEADER in config.php
(e.g. 'CF-Connecting-IP') tells the throttle which header carries the
real address; invalid or absent values fall back to REMOTE_ADDR, and
configs that predate the setting behave as before. Only for origins
unreachable except through the proxy — documented in the README, which
also covers the mod_remoteip/real_ip alternative.

Claude-Session: https://claude.ai/code/session_01SePf61iazhCLJBefdRLBDm
2026-07-30 21:17:46 -04:00
Harwinder Sekhon 3c5d883f6c Fix blank page from stale cached assets; surface errors
A browser or CDN holding an assets/app.js from before the shell's element
IDs were renamed runs it against the newer index.php, where it grabs
btn-logout/btn-rescan/nav-user/nav-username/app — all now null — and throws
before it can unhide #app-shell. The result is a blank page whose only clue
is a TypeError in the console.

- index.php: asset() stamps local css/js with the file mtime, so a deploy is
  a new URL and a stale copy can't be paired with a newer shell.
- index.php: install an error handler ahead of every other script (capture
  phase, so failed resource loads are caught too) that paints uncaught
  errors, rejections and load failures into a #boot-error panel. It works
  even when app.js itself is what failed, which is this case.
- includes/db.php: add a register_shutdown_function fatal handler, since
  set_exception_handler never sees parse errors, exhausted memory or a
  failed require — those were ending requests with an empty body. Both
  handlers flush partial output and answer in JSON; display_errors stays off
  so a warning can't corrupt a JSON body.
- New optional DEBUG config flag adds exception type, file:line and trace to
  those responses. config.php is gitignored, so installs without the setting
  default to false.

Claude-Session: https://claude.ai/code/session_014oyi9DMdhALrGz989MNNZJ
2026-07-27 14:22:23 -04:00
Harwinder Sekhon 91edba56d6 Remove course thumbnails and the ffmpeg dependency
Course cards go back to the colour-block art that previously sat under the
captured frame, so the UI is unchanged apart from losing the photo.

- Drop thumb.php, generateThumbnails(), captureThumbnail() and
  ensureThumbnailColumns(), plus the first-video tracking the scanner only
  kept to pick a frame source.
- Drop the ffprobe fallback in probeDuration(); the pure-PHP mvhd parse is
  now the only scan-time probe, with the player's report on first play as
  the remaining fallback.
- Drop THUMBNAILS_DIR/THUMBNAIL_SEEK_PERCENT from config, the
  thumbnail_* columns from schema.sql, the thumbnail counters from scan
  output, .thumb img from the stylesheet, and the matching README parts.

Existing databases keep two now-unused folders.thumbnail_* columns; they
are inert and can be dropped by hand if wanted.

Claude-Session: https://claude.ai/code/session_014oyi9DMdhALrGz989MNNZJ
2026-07-27 14:06:06 -04:00
Harwinder Sekhon c4c88e52da UI improved 2026-07-27 13:58:47 -04:00
Harwinder Sekhon 4bd3d63a76 Installer: configurable videos folder
install.php now takes the videos path (form field / --videos= CLI flag),
persists it into config.php, and creates the directory if missing.
config.example.php default changed from a dev path to /srv/courses.
2026-07-11 11:06:45 -04:00
Harwinder Sekhon 3ae6c1ae2e CourseHub: AJAX video course player (PHP + MySQL + Bootstrap + Plyr)
Scans nested course folders for mp4s, tracks per-user resume position
and per-folder progress. SPA frontend with hash routing over a JSON API;
range-request streaming via stream.php.
2026-07-11 10:25:35 -04:00