Cleans a single path segment (no /): whitespace runs become a single -, unsafe ASCII is
dropped, runs of - collapse to one, and leading/trailing - are trimmed.
Decode each maximal run of %XX escapes as one UTF-8 unit, dropping a run that isnβt valid
UTF-8 (rather than emitting U+FFFD). Non-escape characters pass through. Mirrors the migrationβs
safe_percent_decode, so storage and lookup agree on inputs like /%FF.
Looks up a non-deleted page in course_id whose stored url_path matches url_path in
either the decoded-canonical or the legacy fully-encoded form (see
url_path_lookup_candidates). Shared by the page lookup and its tests so both exercise
the same candidate-matching logic.
Re-keys a custom peer-review config and its questions onto new_exercise_id with a fresh config id
and fresh question ids, producing an independent copy for a within-page exercise duplicate so it
does not collapse onto the original exerciseβs config (#148). All questions must belong to
config.
Whether ch is an ASCII char that URL_PATH_ENCODE_SET would encode. The setβs membership
test is crate-private, so probe it via utf8_percent_encode (which encodes an ASCII char iff
itβs in the set). ASCII-only: the caller keeps non-ASCII verbatim.
Legacy lookup candidate: unsafe ASCII percent-encoded, non-ASCII kept decoded β the form used
before paths were stored stripped. Lets pages written that way (notably exam pages, which the
cleanup migration skips) still resolve.
Canonical storage/lookup form: decode %xx, then per /-segment turn whitespace into -,
strip the unsafe ASCII in URL_PATH_ENCODE_SET, and collapse/trim dashes. Case, non-ASCII and
/, -, ., _, ~ are kept. Unsafe chars are removed, not %xx-encoded, so every save
funnels through here and paths canβt re-accumulate escapes. Mirrors clean_url_path (page
migration) and the frontend cleanUrlPath; keep the three in agreement.
Gives questions fresh ids pointing at target_config_id when it differs from source_config_id
(i.e. they are being cloned onto a different config); otherwise returns them untouched so the
upsert reuses the existing rows.
Makes the order numbers and chapter ids to match in the db whatβs in the page objects
Assumes that all pages belong to the given course id
Also assumes the list of pages includes all nondeleted pages in the course, except chapter front pages, which cannot be moved, otherwise we will end up with random order numbers
Stored url_path forms to try for a requested path: the strip-canonical form plus the two
legacy encoded forms, deduped (an ASCII-only safe path yields a single candidate).