Repository navigation
fix(theme): keep one session store for the selected theme #278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,49 +53,68 @@ class xos_opal_ThemeFactory | |
| public $allowUserSelection = true; | ||
|
|
||
| /** | ||
| * Instantiate the specified theme | ||
| * @param array $options | ||
| * @param array $initArgs | ||
| * @return null|xos_opal_Theme | ||
| * Pick the theme folder when the caller did not name one. | ||
| * | ||
| * Order: xoops_theme_select from POST, then GET (remembered in | ||
| * $_SESSION['xoopsUserTheme']), then the remembered session theme, | ||
| * then $this->defaultTheme. The session key is the one the kernel's | ||
| * theme selector and login also use, so there is a single store. | ||
| * | ||
| * @param array $options createInstance() options | ||
| * @return array $options with 'folderName' resolved | ||
| */ | ||
| public function createInstance($options = [], $initArgs = []) | ||
| protected function resolveFolderName(array $options): array | ||
| { | ||
| // Grab the theme folder from request vars if present. | ||
| // Use === '' rather than empty() — a theme directory literally | ||
| // named "0" is legitimate (legacy XOOPS supports all-numeric | ||
| // theme names), and empty('0') === true would silently drop it. | ||
| if (!isset($options['folderName']) || $options['folderName'] === '') { | ||
| $req = xoops_validateThemeName(Request::getString('xoops_theme_select', '', 'POST')); | ||
| if ($req === '') { | ||
| $req = xoops_validateThemeName(Request::getString('xoops_theme_select', '', 'GET')); | ||
| } | ||
| if ($req !== '' && $this->isThemeAllowed($req)) { | ||
| $options['folderName'] = $req; | ||
| if (isset($_SESSION) && $this->allowUserSelection) { | ||
| $_SESSION[$this->xoBundleIdentifier]['defaultTheme'] = $req; | ||
| } | ||
| } elseif (isset($_SESSION[$this->xoBundleIdentifier]['defaultTheme'])) { | ||
| // Stale session theme names can outlive an admin change | ||
| // to the allowed list; validate AND check membership | ||
| // before reaching the path-construction code below. | ||
| // xoops_validateThemeValue() — scalar-gated; a session | ||
| // poisoned with an array / object never reaches the | ||
| // (string) cast (which would emit a warning AND let | ||
| // "Array" pass the path-safety check downstream). | ||
| $sessionTheme = xoops_validateThemeValue($_SESSION[$this->xoBundleIdentifier]['defaultTheme']); | ||
| $options['folderName'] = ($sessionTheme !== '' && $this->isThemeAllowed($sessionTheme)) | ||
| ? $sessionTheme | ||
| : $this->defaultTheme; | ||
| } elseif (!isset($options['folderName']) || $options['folderName'] === '' || !$this->isThemeAllowed($options['folderName'])) { | ||
| $options['folderName'] = $this->defaultTheme; | ||
| if (isset($options['folderName']) && $options['folderName'] !== '') { | ||
| return $options; | ||
| } | ||
| $req = xoops_validateThemeName(Request::getString('xoops_theme_select', '', 'POST')); | ||
| if ($req === '') { | ||
| $req = xoops_validateThemeName(Request::getString('xoops_theme_select', '', 'GET')); | ||
| } | ||
| if ($req !== '' && $this->isThemeAllowed($req)) { | ||
| $options['folderName'] = $req; | ||
| if (isset($_SESSION) && $this->allowUserSelection) { | ||
| $_SESSION['xoopsUserTheme'] = $req; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Concurrent theme choices are lost When two requests for the same session select different themes concurrently, a page request selecting alpha and a selector request selecting beta can both read the same session snapshot. The custom session handler writes the entire serialized session without locking, so whichever request saves last overwrites the other choice; later pages can show the losing theme. Add per-session locking or conflict-safe updates for the shared theme value. Serialize session reads and writes with a per-session lock, or merge theme updates without overwriting newer session state. Prompt for AI agentsIn `htdocs/class/theme.php` at line 81:
**Concurrent theme choices are lost**
When two requests for the same session select different themes concurrently, a page request selecting alpha and a selector request selecting beta can both read the same session snapshot. The custom session handler writes the entire serialized session without locking, so whichever request saves last overwrites the other choice; later pages can show the losing theme. Add per-session locking or conflict-safe updates for the shared theme value.
Serialize session reads and writes with a per-session lock, or merge theme updates without overwriting newer session state.
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| } elseif (isset($_SESSION['xoopsUserTheme'])) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium · Remembered themes are lost When a returning session has its selected theme only under the pre-upgrade factory key, When Also at Prompt for AI agentsIn `htdocs/class/theme.php` at line 83:
**Remembered themes are lost**
When a returning session has its selected theme only under the pre-upgrade factory key, `resolveFolderName()` checks only `xoopsUserTheme` and falls back to `defaultTheme`, so returning users lose their remembered selection after upgrade.
When `xoopsUserTheme` is absent, read and validate the legacy factory `defaultTheme` session value before falling back to the configured default.
Also at `htdocs/class/theme.php:91`. |
||
| // Stale session theme names can outlive an admin change | ||
| // to the allowed list; validate AND check membership | ||
| // before reaching the path-construction code below. | ||
| // xoops_validateThemeValue() — scalar-gated; a session | ||
| // poisoned with an array / object never reaches the | ||
| // (string) cast (which would emit a warning AND let | ||
| // "Array" pass the path-safety check downstream). | ||
| $sessionTheme = xoops_validateThemeValue($_SESSION['xoopsUserTheme']); | ||
| $options['folderName'] = ($sessionTheme !== '' && $this->isThemeAllowed($sessionTheme)) | ||
| ? $sessionTheme | ||
| : $this->defaultTheme; | ||
| } else { | ||
| $options['folderName'] = $this->defaultTheme; | ||
| } | ||
|
|
||
| return $options; | ||
| } | ||
|
|
||
| /** | ||
| * Instantiate the specified theme | ||
| * @param array $options | ||
| * @param array $initArgs | ||
| * @return null|xos_opal_Theme | ||
| */ | ||
| public function createInstance($options = [], $initArgs = []) | ||
| { | ||
| $options = $this->resolveFolderName($options); | ||
| // Validate $options['folderName'] AND the defaultTheme property | ||
| // unconditionally, after every resolution branch. Two paths | ||
| // bypass the inner-if block above and could still produce an | ||
| // unsafe path segment: | ||
| // unconditionally, after every resolution branch. Two inputs | ||
| // reach this point without the request/session validation in | ||
| // resolveFolderName() and could still produce an unsafe path | ||
| // segment: | ||
| // - A non-standard caller may have supplied a poisoned | ||
| // folderName directly (skipping the empty()-branch). | ||
| // folderName directly (returned unchanged). | ||
| // - $this->defaultTheme is a public property; any caller | ||
| // can overwrite it before createInstance() runs. | ||
| // xoops_validateThemeValue() — scalar gate + path/HTML validate | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.