Skip to content

Language::setLang(): add opt-out for cookie/session persistence #29

Description

@flangfeldt

Context

Language::setLang() currently always writes the chosen language to $_SESSION['language'] AND sends a Set-Cookie: language=…; Max-Age=2592000; Path=/ response header (vendor/neverwoods/bili/classes/Bili/Language.php:179-194). That behavior makes sense for stateful web apps, but it's a problem in stateless contexts.

We just hit this in the Celery REST API. A consumer calls a single endpoint that exposes ?language=es for an inline preview (e.g. POST /v3/time-off-request-description). Bili's setLang('espanol-utf-8') runs, sets a 30-day cookie, and from that point on every subsequent call from the same client (Postman, browser, curl with a jar) ships Cookie: language=espanol-utf-8 back to the API. LanguageMiddleware re-instantiates the singleton on every request, the constructor's getLang() picks up $_COOKIE['language'], and the whole API responds in Spanish — including endpoints that have nothing to do with that one preview parameter. From the consumer's point of view the language is "leaking" across endpoints.

REST APIs aren't supposed to carry language preference in cookies or sessions; the standard mechanism is the Accept-Language request header (RFC 9110 §12.5.4). So the API needs to ignore Bili's session/cookie path entirely while still being able to call setLang() for the in-process effect of loading the language file.

Proposal

Add an optional second argument to Language::setLang() controlling persistence:

public function setLang(string $strLang, bool $blnPersist = true)
  • $blnPersist = true (default): unchanged behavior. Writes $_SESSION['language'] and the 30-day cookie. Existing callers behave exactly as today.
  • $blnPersist = false: load the language file in-process only. Skip the setcookie() call. Skip the $_SESSION['language'] write.

Same idea could optionally be applied to the constructor / getLang() paths so the singleton can be initialized in a "stateless" mode that also ignores incoming $_SESSION['language'] / $_COOKIE['language'] when picking the active language. That's a nice-to-have; the setLang() flag is the immediate need.

Backward compatibility

Fully backward compatible: the default true preserves today's behavior for every existing caller. Only callers that pass false opt into the new path.

Use case

In the Celery web API, LanguageMiddleware (and a small app-level ApiLanguage helper we're adding) will call setLang($strLang, false) after resolving the language from Accept-Language (and from supported ?language= query params on a handful of endpoints). No cookies, no session writes, no cross-endpoint leakage.

Acceptance criteria

  • setLang($lang, false) does not call setcookie('language', …).
  • setLang($lang, false) does not write to $_SESSION['language'].
  • setLang($lang, false) still loads the language file so subsequent Language::get(…) calls return strings in $lang.
  • setLang($lang, true) and setLang($lang) behave exactly as today.
  • A unit test covers each of the three cases.

Related: the Celery web API tracking issue references this from its own implementation plan.

Metadata

Metadata

Assignees

Fields

No fields configured for Feature.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions