Skip to content

improve(core): consolidate theme-config normalisation across runtime readers#81

Merged
mambax7 merged 3 commits into
XOOPS:masterfrom
mambax7:fix/issue-45-theme-config-helpers
May 29, 2026
Merged

improve(core): consolidate theme-config normalisation across runtime readers#81
mambax7 merged 3 commits into
XOOPS:masterfrom
mambax7:fix/issue-45-theme-config-helpers

Conversation

@mambax7
Copy link
Copy Markdown
Contributor

@mambax7 mambax7 commented May 29, 2026

Summary

Single source of truth for theme_set / theme_set_allowed validation so every runtime reader — header render, login, remember-me, theme factory, xoops_getcss, the System theme block, and the kernel theme switcher — sees the same fail-empty contract.

Closes #45.

Extends the closure-based defence PR #44 added to the System theme block into a process-wide guarantee, and plugs three real session-trust / path-construction leaks the issue's table didn't enumerate.

What this PR does

1. Two new helpers in htdocs/include/theme_config.php

xoops_validateThemeName(string $name): string
xoops_resolveThemeConfig(array $xoopsConfig): array
  • xoops_validateThemeName — fail-empty path + HTML safety check (rejects path separators, null bytes, leading dot, .. segments, < > & " '). Spaces and non-ASCII names pass through unchanged so legitimate My Theme and CJK theme directories keep working.
  • xoops_resolveThemeConfig — returns ['theme_set' => string, 'theme_set_allowed' => list<string>] with non-scalar entries dropped, pipe-string allowed lists split, current theme injected into the allowed list when valid but absent, and 'default' as the universal fallback.

Required transitively via include/functions.php.

2. Boundary normalise in include/common.php

Immediately after the file+DB config merge, $xoopsConfig is array_replaced with the resolved pair. Plain reads of those keys in code loaded afterwards (header.php, site-closed.php, the editors, the CSS URL helpers, the smarty resource plugin) are then safe-by-invariant — no per-call-site sweep needed for cosmetic reads.

3. Decision-point callers normalize explicitly

Six callers go through the resolver at the call site, even where the boundary normalise already covers them, so the intent is obvious and the code survives any future caller that hits the path without going through common.php (CLI entry, partial init):

  • htdocs/include/checklogin.php
  • htdocs/include/common.php remember-me branch
  • htdocs/include/site-closed.php
  • htdocs/header.php
  • htdocs/class/xoopsform/formselecttheme.php
  • htdocs/modules/system/blocks/system_blocks.php

4. Kernel theme switcher (separate cache layer)

xoops_getConfigOption('theme_set'*) reads through XoopsConfigHandler's in-memory cache, which is not covered by the boundary normalise. xos_kernel_Xoops2::themeSelect() now:

  • Resolves at the call site.
  • Validates the submitted theme name AND the session-stored theme name before strict-membership checks.
  • Uses in_array(..., true) so a theme literally named 0 doesn't coerce to false against arbitrary allowed entries.

5. htdocs/class/theme.php hardening (three real bugs)

  • Session-trust branch. $_SESSION[$this->xoBundleIdentifier]['defaultTheme'] in createInstance() was previously assigned to $options['folderName'] without any validation or membership check, then used as a path segment when constructing $testPath. A stale session value (admin removed the theme from the allowed list, manually-poisoned session) reached file_exists() unvalidated. Now validated AND required to pass isThemeAllowed() before assignment.
  • Mid-request writes. The two writes to $GLOBALS['xoopsConfig']['theme_set'] inside createInstance() are piped through xoops_validateThemeName() so the boundary invariant set by common.php holds for every downstream reader.
  • Loose in_array. isThemeAllowed() now uses in_array(..., true) — a theme literally named 0 no longer matches every allowed entry.

6. xoops_getcss() defence in depth

Validates its $theme parameter and falls back via the resolver when empty, so a future caller passing raw input cannot reach the <link href=> attribute or filesystem checks unvalidated.

7. Pre-commit regression sniffs

Two new sniffs in .githooks/pre-commit:

  • Loose in_array(..., $xoopsConfig['theme_set_allowed']) — strict comparison required.
  • Raw assignment $xoopsThemeFactory->allowedThemes = $xoopsConfig['theme_set_allowed'] — must go through the resolver.

8. Tests

tests/unit/htdocs/include/ThemeConfigTest.php covers the full validation matrix:

  • Rejection cases (empty, leading dot, /, \, null byte, parent-dir segment, HTML metachars).
  • Theme literally named 0.
  • Spaces and non-ASCII (CJK).
  • Pipe-string theme_set_allowed splitting.
  • Non-scalar allowed entries filtered out.
  • Current theme injected when valid but absent from allowed list.
  • Corrupted current theme falls back to default.
  • Allowed list deduped.
  • Non-array, non-string theme_set_allowed treated as empty.

Files changed (12)

New (2)

  • htdocs/include/theme_config.php
  • tests/unit/htdocs/include/ThemeConfigTest.php

Modified (10)

  • htdocs/include/common.php
  • htdocs/include/functions.php
  • htdocs/include/checklogin.php
  • htdocs/include/site-closed.php
  • htdocs/header.php
  • htdocs/class/theme.php
  • htdocs/class/xoopsform/formselecttheme.php
  • htdocs/class/xoopskernel.php
  • htdocs/modules/system/blocks/system_blocks.php
  • .githooks/pre-commit

Out of scope

Same exclusions the issue called out — admin-side theme-list editors (the saved-value path) and directory-name regex vs on-disk existence checks are not addressed here.

Test plan

  • CI green: PHPUnit + PHPStan + Psalm gates.
  • ThemeConfigTest covers all 22 cases listed above.
  • Manual: install with default + one custom theme; switch via the System block; log out / in; verify xoopsUserTheme round-trips.
  • Manual: corrupt xoops_config.theme_set_allowed with a non-string row; visit any page — front + admin must still render with the default fallback (no fatal, no warning).
  • Manual: visit the site with ?xoops_theme_select=../etc/passwd — must be rejected at the factory, fall back to default.
  • Manual: log in with a users.theme value of My Theme (with space) — must round-trip through checklogin.php and common.php remember-me.
  • bash .githooks/pre-commit against a synthetic staged change that re-introduces a loose in_array on theme_set_allowed — must reject.

References

Summary by Sourcery

Centralize theme configuration normalization and harden theme selection across the runtime to enforce consistent, validated theme_set and theme_set_allowed usage.

Bug Fixes:

  • Prevent unsafe theme names from reaching filesystem paths, session-driven theme selection, and HTML output by validating and normalizing theme configuration at all decision points.
  • Fix loose in_array checks in theme allowance logic so themes named "0" no longer match unintended entries and stale session themes cannot bypass the allowed list.

Enhancements:

  • Introduce shared helpers to validate theme names and resolve theme_set/theme_set_allowed, and apply them at the configuration boundary and key runtime call sites including header, login, remember-me, and theme selectors.
  • Align theme factory, kernel theme switcher, and form theme selector with the centralized theme configuration helpers for consistent behavior and safer defaults across the application.

Tests:

  • Add ThemeConfigTest to cover the validation and normalization behavior of the new theme configuration helpers, including edge cases and corrupted configuration inputs.

Chores:

  • Extend the pre-commit hook to flag loose in_array checks on theme_set_allowed and raw assignments to the theme factory's allowedThemes, enforcing the new normalization patterns.

Summary by CodeRabbit

  • New Features

    • Centralized theme validation and config normalization helpers added.
  • Bug Fixes

    • Stricter theme name/value validation and safer handling across initialization, login, session, template selection, and asset lookup.
    • Enforced strict allowed-theme matching and consistent fallback/default behavior, preserving valid edge-case names like "0".
  • Tests

    • Comprehensive unit tests for theme validation and config resolution.
  • Chores

    • Enhanced pre-commit lint checks for staged PHP changes.

Review Change Stack

mambax7 added 2 commits May 29, 2026 02:03
…readers

Single source of truth for theme_set / theme_set_allowed validation so
every runtime reader (header, login, remember-me, theme factory,
xoops_getcss, system theme block, kernel theme switcher) sees the same
fail-empty contract. Closes XOOPS#45.

Adds two helpers in include/theme_config.php (required transitively by
include/functions.php):

  xoops_validateThemeName(string): string
      fail-empty path + HTML safety check (rejects path separators,
      null bytes, leading dot, .. segments, < > & " '); spaces and
      non-ASCII names pass through unchanged so legitimate "My Theme"
      and CJK theme directories keep working.

  xoops_resolveThemeConfig(array): array
      returns ['theme_set' => string, 'theme_set_allowed' => list<string>]
      with non-scalar entries dropped, pipe-string allowed lists split,
      current theme injected into allowed list when valid but absent,
      and 'default' as the universal fallback.

include/common.php applies the resolver via array_replace immediately
after the file+DB config merge, so plain reads of those keys in code
loaded afterwards (header.php, site-closed.php, the editors, the CSS
URL helpers, the smarty resource plugin) are safe-by-invariant.

Decision-point callers go through the resolver explicitly even when
the boundary normalise covers them, so intent is obvious at the call
site and the code survives any future caller that hits the path
without going through common.php (CLI entry, partial init):

  - htdocs/include/checklogin.php
  - htdocs/include/common.php remember-me branch
  - htdocs/include/site-closed.php
  - htdocs/header.php
  - htdocs/class/xoopsform/formselecttheme.php
  - htdocs/modules/system/blocks/system_blocks.php

xoops_getConfigOption('theme_set'*) has its own cache layer in
XoopsConfigHandler and is NOT covered by the boundary normalise.
xos_kernel_Xoops2::themeSelect() now resolves at the call site and
strict-compares membership; the session-stored theme name is
validated before being trusted.

theme.php hardening:

  - $_SESSION[xoBundleIdentifier]['defaultTheme'] in createInstance()
    was previously assigned to $options['folderName'] without
    validation or membership check, then used as a path segment in
    $testPath. Validate the session value AND require isThemeAllowed()
    before assigning.
  - The two mid-request writes to $GLOBALS['xoopsConfig']['theme_set']
    are piped through xoops_validateThemeName() so the boundary
    invariant set by common.php holds for every downstream reader.
  - isThemeAllowed() now uses strict in_array(..., true) - a theme
    literally named "0" no longer matches every allowed entry.

xoops_getcss() validates its $theme parameter and falls back via the
resolver when empty, so a future caller passing raw input cannot
reach the <link href=> attribute or filesystem checks unvalidated.

The .githooks/pre-commit hook gains two new sniffs to prevent
regression: loose in_array() on theme_set_allowed, and raw assignment
of $xoopsConfig['theme_set'*] to theme-factory properties.

Tests: tests/unit/htdocs/include/ThemeConfigTest.php covers the
validation matrix (rejection cases, theme named "0", spaces,
non-ASCII, pipe-string splitting, non-scalar filtering, current theme
injection, corrupted-current fallback to default, deduping,
non-array-non-string allowed entries).

Refs:
  - PR XOOPS#44 (the block-level closure that this consolidates)
  - getVar() 'n'-format note in repo CLAUDE.md (used in
    common.php / checklogin.php remember-me branches to avoid
    rejecting "&" entity-escaped by the default 's' format)
Copilot AI review requested due to automatic review settings May 29, 2026 06:15
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented May 29, 2026

Reviewer's Guide

Introduces centralized, side‑effect‑free helpers for validating and resolving theme configuration, applies them at the config boundary and at all theme decision points, hardens session and request handling around theme selection, and adds tests and pre‑commit checks to prevent regressions in theme_set / theme_set_allowed handling.

Sequence diagram for runtime theme selection and validation

sequenceDiagram
    actor User
    participant Browser
    participant XoopsKernel as xos_kernel_Xoops2
    participant ThemeFactory as xos_opal_ThemeFactory
    participant ThemeHelpers as theme_config_helpers

    User->>Browser: Submit xoops_theme_select
    Browser->>XoopsKernel: HTTP POST xoops_theme_select

    XoopsKernel->>ThemeHelpers: xoops_resolveThemeConfig(theme_set, theme_set_allowed from xoops_getConfigOption)
    ThemeHelpers-->>XoopsKernel: theme_set_allowed

    XoopsKernel->>ThemeHelpers: xoops_validateThemeName(xoops_theme_select)
    ThemeHelpers-->>XoopsKernel: validated themeSelect

    alt [themeSelect !== '' && in_array(themeSelect, theme_set_allowed, true)]
        XoopsKernel->>XoopsKernel: xoops_setConfigOption(theme_set, themeSelect)
        XoopsKernel->>XoopsKernel: set $_SESSION[xoopsUserTheme]
    else [fallback to session theme]
        XoopsKernel->>ThemeHelpers: xoops_validateThemeName($_SESSION[xoopsUserTheme])
        ThemeHelpers-->>XoopsKernel: validated sessionTheme
        XoopsKernel->>XoopsKernel: in_array(sessionTheme, theme_set_allowed, true)
        XoopsKernel->>XoopsKernel: xoops_setConfigOption(theme_set, sessionTheme)
    end

    XoopsKernel->>ThemeFactory: createInstance(folderName from xoopsConfig['theme_set'])
    ThemeFactory->>ThemeHelpers: xoops_validateThemeName(options['folderName'])
    ThemeHelpers-->>ThemeFactory: validated folderName
    ThemeFactory-->>Browser: Render theme
Loading

Flow diagram for centralized theme configuration normalisation

flowchart LR
    subgraph ConfigSource
        A[xoopsConfig raw]
        B[xoops_getConfigOption]
    end

    A --> C[xoops_resolveThemeConfig]
    C --> D[common.php
array_replace xoopsConfig]

    D --> H[header.php
xos_opal_ThemeFactory]
    D --> I[site-closed.php
xos_opal_ThemeFactory]
    D --> J[checklogin.php]
    D --> K[common.php
remember-me]
    D --> L[formselecttheme.php]
    D --> M[xoops_getcss]
    D --> N[system_blocks.php
b_system_themes_show]
    D --> O[theme.php
createInstance]

    B --> C2[xoops_resolveThemeConfig]
    C2 --> P[xoopskernel.php
themeSelect]

    subgraph Validators
        C
        C2
        Q[xoops_validateThemeName]
    end

    J --> Q
    K --> Q
    M --> Q
    O --> Q
    P --> Q
Loading

File-Level Changes

Change Details Files
Centralize theme configuration validation and resolution into reusable helpers and load them globally.
  • Add xoops_validateThemeName() to perform fail-empty path and HTML safety validation on theme names while allowing spaces and non-ASCII characters.
  • Add xoops_resolveThemeConfig() to normalize theme_set/theme_set_allowed into a safe pair, handling pipe-separated strings, non-scalar entries, defaults, and ensuring the current theme is present in the allowed list.
  • Guard helper definitions with function_exists() and wire the new file into the core bootstrap via include/functions.php.
htdocs/include/theme_config.php
htdocs/include/functions.php
Normalize theme configuration once at bootstrap and ensure downstream reads use the normalized contract, including remember-me and login flows.
  • After merging file and DB config in common.php, replace theme_set and theme_set_allowed in $xoopsConfig with the result of xoops_resolveThemeConfig().
  • Update remember-me logic in common.php to validate the user theme (using raw getVar('theme','n')), enforce strict in_array() membership, and avoid HTML-escaped values.
  • Update checklogin.php to validate the user’s theme, use raw getVar('theme','n'), and enforce strict in_array() against the allowed themes.
htdocs/include/common.php
htdocs/include/checklogin.php
Update all theme factory and renderer entry points to consume normalized theme config and validated theme names.
  • In header.php and site-closed.php, resolve theme config and feed allowedThemes/defaultTheme to xos_opal_ThemeFactory via the normalized pair instead of raw $xoopsConfig values.
  • In formselecttheme.php, build the select options from xoops_resolveThemeConfig() rather than trusting $GLOBALS['xoopsConfig']['theme_set_allowed'].
htdocs/header.php
htdocs/include/site-closed.php
htdocs/class/xoopsform/formselecttheme.php
Harden the kernel theme switcher against cached config, invalid request values, and stale session themes.
  • In xoopskernel::themeSelect(), resolve theme_set and theme_set_allowed via xoops_resolveThemeConfig() using xoops_getConfigOption() values to bypass its cache layer safely.
  • Validate POST-submitted theme names and session-stored themes with xoops_validateThemeName() before checking strict membership in the allowed list.
  • Use in_array(..., true) when checking allowed themes for both request and session paths, and only update config when validation and membership checks pass.
htdocs/class/xoopskernel.php
Strengthen theme factory behavior around request, session, and global config writes for theme selection.
  • Validate POST/GET xoops_theme_select values before using them, and only accept them when they pass isThemeAllowed().
  • Validate session-stored defaultTheme and require it to be allowed before using it, otherwise fall back to the factory default.
  • When writing back to $GLOBALS['xoopsConfig']['theme_set'], pass through xoops_validateThemeName() and default to 'default' on failure to maintain the boundary invariant.
  • Change isThemeAllowed() to use strict in_array() to avoid coercion issues (e.g., theme named '0').
htdocs/class/theme.php
Replace the System theme block’s inline normalization logic with calls to the shared helpers.
  • Remove local closure implementations of theme validation and theme config resolution inside b_system_themes_show().
  • Use xoops_resolveThemeConfig() to obtain currentTheme and allowedThemes for the block, relying on the shared helper for all normalization rules.
htdocs/modules/system/blocks/system_blocks.php
Add defence in depth for CSS theme resolution and enforce regression checks via git hooks.
  • Update xoops_getcss() to validate its $theme parameter, and when empty, resolve the current theme via xoops_resolveThemeConfig() instead of trusting raw config.
  • Extend the pre-commit hook to sniff for loose in_array() uses with theme_set_allowed and for direct assignment of the raw allowed list to the theme factory, enforcing use of the resolver.
htdocs/include/functions.php
.githooks/pre-commit
Introduce unit tests to lock in the theme validation and resolution behavior, including edge and corruption cases.
  • Add ThemeConfigTest covering xoops_validateThemeName() rejection and acceptance matrix (path separators, null bytes, HTML metacharacters, spaces, non-ASCII, theme named '0').
  • Add ThemeConfigTest coverage for xoops_resolveThemeConfig() across pipe-string splitting, non-scalar filtering, deduplication, current theme injection, corrupted values, and fallback behavior.
tests/unit/htdocs/include/ThemeConfigTest.php

Assessment against linked issues

Issue Objective Addressed Explanation
#45 Introduce shared, eagerly-loaded helpers to normalize theme_set and theme_set_allowed so normalization logic is no longer local to the System theme block.
#45 Update all runtime readers of theme_set and theme_set_allowed (including the six call sites listed in the issue) to use the shared normalization helpers, ensuring consistent behavior between the System theme block and all runtime checks.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Walkthrough

Adds scalar-gated theme validation and a resolver, normalizes theme settings at startup, replaces duplicated defensive validation across UI and runtime (login, kernel, factory, templates), adds unit tests, and extends pre-commit sniffs to prevent loose checks and direct raw-config assignments.

Changes

Theme Configuration Validation and Runtime Consolidation

Layer / File(s) Summary
Theme validation & resolver
htdocs/include/theme_config.php
Adds xoops_validateThemeName(), xoops_validateThemeValue(), and xoops_resolveThemeConfig() to validate theme names and normalize theme_set/theme_set_allowed with parsing, filtering, fallbacks, and deduplication.
Startup normalization and wiring
htdocs/include/common.php, htdocs/include/functions.php, htdocs/header.php
Require/load the new helper, call xoops_resolveThemeConfig() during config hydration, and use it as the canonical source for theme settings; xoops_getcss() validates theme input and falls back via resolver.
UI selectors and templates
htdocs/class/xoopsform/formselecttheme.php, htdocs/modules/system/blocks/system_blocks.php, htdocs/include/site-closed.php
Form options and template variables now source allowed-theme lists and defaults from xoops_resolveThemeConfig() instead of raw $xoopsConfig.
User selection and session validation
htdocs/include/checklogin.php, htdocs/include/common.php, htdocs/class/theme.php, htdocs/class/xoopskernel.php
POST/GET/session theme values are validated with helpers and accepted only when strictly present in the resolver’s allowed list (in_array(..., true)); factory and kernel update global theme only after normalization.
Tests & pre-commit sniffs
tests/unit/htdocs/include/ThemeConfigTest.php, .githooks/pre-commit
Comprehensive PHPUnit tests for both validators and the resolver (covering edge cases including "0") and new pre-commit sniffs that flag loose in_array usage and direct $xoopsConfig -> ->allowedThemes/->defaultTheme assignments.
Template compile-id tweak
htdocs/class/template.php
Replace empty() check with explicit null === / '' === checks to preserve theme directory "0" values when computing compile-id.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant common.php
    participant theme_config.php
    participant checklogin.php
    participant xoopskernel.php

    User->>common.php: Load application/config
    common.php->>theme_config.php: xoops_resolveThemeConfig(xoopsConfig)
    theme_config.php-->>common.php: {theme_set, theme_set_allowed}
    common.php->>common.php: Update xoopsConfig with normalized pair

    User->>checklogin.php: Submit login with theme
    checklogin.php->>theme_config.php: xoops_validateThemeName(posted_theme)
    theme_config.php-->>checklogin.php: validated_name or ''
    checklogin.php->>common.php: in_array(validated_name, resolved.allowed, true)
    checklogin.php->>xoopskernel.php: store session theme if valid

    User->>xoopskernel.php: Subsequent request with session theme
    xoopskernel.php->>theme_config.php: xoops_validateThemeValue(session_theme)
    theme_config.php-->>xoopskernel.php: validated_value or ''
    xoopskernel.php->>common.php: in_array(validated_value, resolved.allowed, true)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • XOOPS/XoopsCore27#44: Introduced local defensive validation closures in b_system_themes_show() that this PR consolidates into shared helpers.
  • XOOPS/XoopsCore27#33: Also touched xos_kernel_Xoops2::themeSelect(); related to theme-selection control flow.
  • XOOPS/XoopsCore27#48: Extends the same .githooks/pre-commit framework; this PR adds theme-config specific sniffs.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: centralizing theme-config normalization across runtime readers, which is the core objective of the PR.
Linked Issues check ✅ Passed All coding requirements from issue #45 are met: shared helpers added, all six call sites updated, validation/normalization applied, and non-scalar entries handled.
Out of Scope Changes check ✅ Passed All changes directly support the consolidation objective; pre-commit hooks and template.php fix are adjacent hardening consistent with the main goal.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

❌ Patch coverage is 42.16867% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 18.19%. Comparing base (62d52ff) to head (88813ff).
⚠️ Report is 8 commits behind head on master.

Files with missing lines Patch % Lines
htdocs/class/theme.php 0.00% 15 Missing ⚠️
htdocs/class/xoopskernel.php 0.00% 9 Missing ⚠️
htdocs/include/site-closed.php 0.00% 7 Missing ⚠️
htdocs/include/common.php 0.00% 4 Missing ⚠️
htdocs/include/functions.php 0.00% 4 Missing ⚠️
htdocs/class/xoopsform/formselecttheme.php 0.00% 3 Missing ⚠️
htdocs/modules/system/blocks/system_blocks.php 0.00% 3 Missing ⚠️
htdocs/include/checklogin.php 0.00% 2 Missing ⚠️
htdocs/class/template.php 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master      #81      +/-   ##
============================================
+ Coverage     18.13%   18.19%   +0.05%     
- Complexity     7854     7861       +7     
============================================
  Files           666      667       +1     
  Lines         43208    43259      +51     
============================================
+ Hits           7837     7872      +35     
- Misses        35371    35387      +16     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request centralizes theme configuration validation and normalization by introducing helper functions in htdocs/include/theme_config.php and applying them across various files to ensure path and HTML safety. Feedback on the changes suggests optimizing xoops_getcss by directly accessing the already-normalized $GLOBALS['xoopsConfig']['theme_set'] to avoid redundant calls, and using is_scalar instead of is_string when resolving theme_set to prevent numeric theme names like 0 from being incorrectly rejected.

Comment on lines +897 to 899
if ($theme === '') {
$theme = xoops_resolveThemeConfig($GLOBALS['xoopsConfig'] ?? [])['theme_set'];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since $GLOBALS['xoopsConfig'] is already normalized at the configuration-merge boundary in common.php (via xoops_resolveThemeConfig), calling xoops_resolveThemeConfig again here is redundant and introduces unnecessary performance overhead on every xoops_getcss call. Directly accessing the already-normalized theme_set key with a fallback is more efficient.

    if ($theme === '') {
        $theme = $GLOBALS['xoopsConfig']['theme_set'] ?? 'default';
    }

Comment thread htdocs/include/theme_config.php Outdated
Comment on lines +120 to +122
$currentTheme = is_string($rawCurrentTheme)
? xoops_validateThemeName($rawCurrentTheme)
: '';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If theme_set is configured as an integer (e.g., 0 due to database driver casting or custom overrides), is_string($rawCurrentTheme) will evaluate to false, causing it to incorrectly fall back to 'default'. Using is_scalar and casting to a string ensures that numeric theme names (like 0) are correctly preserved and validated, matching the behavior of theme_set_allowed below.

        $currentTheme    = is_scalar($rawCurrentTheme)
            ? xoops_validateThemeName((string) $rawCurrentTheme)
            : '';

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • You’re calling xoops_resolveThemeConfig() in multiple hot paths (common.php boundary, header.php, site-closed.php, kernel themeSelect, formselecttheme, xoops_getcss); consider either memoizing the normalised pair or reusing the boundary-normalised $xoopsConfig to avoid recomputing the same normalization several times per request.
  • Common/bootstrap paths such as include/common.php, class/xoopskernel.php, and class/theme.php now depend on xoops_validateThemeName() / xoops_resolveThemeConfig(); it may be safer to require include/theme_config.php explicitly in those entry points rather than relying on the transitive include from include/functions.php to avoid edge cases where load order changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You’re calling `xoops_resolveThemeConfig()` in multiple hot paths (common.php boundary, header.php, site-closed.php, kernel themeSelect, formselecttheme, xoops_getcss); consider either memoizing the normalised pair or reusing the boundary-normalised `$xoopsConfig` to avoid recomputing the same normalization several times per request.
- Common/bootstrap paths such as `include/common.php`, `class/xoopskernel.php`, and `class/theme.php` now depend on `xoops_validateThemeName()` / `xoops_resolveThemeConfig()`; it may be safer to require `include/theme_config.php` explicitly in those entry points rather than relying on the transitive include from `include/functions.php` to avoid edge cases where load order changes.

## Individual Comments

### Comment 1
<location path="htdocs/class/theme.php" line_range="88" />
<code_context>
+            // Pipe writes back to $GLOBALS['xoopsConfig'] through the
+            // validator so the boundary-normalised invariant set by
+            // common.php holds for every downstream reader.
+            $GLOBALS['xoopsConfig']['theme_set'] = xoops_validateThemeName((string) $options['folderName']) ?: 'default';
         }
         $testPath = isset($options['themesPath'])
</code_context>
<issue_to_address>
**issue (bug_risk):** The fallback to literal 'default' can desync $xoopsConfig['theme_set'] from the factory’s defaultTheme and the configured default theme.

If $options['folderName'] fails validation, this forces xoopsConfig['theme_set'] to the hard-coded 'default', even when the configured default theme (and $this->defaultTheme) is different. This can desync the global config from the theme actually in use and may point to a non-existent theme. Prefer using $this->defaultTheme (or the resolved theme from xoops_resolveThemeConfig) instead of the literal 'default' to stay aligned with admin configuration.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread htdocs/class/theme.php Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR centralizes runtime validation/normalization of theme_set and theme_set_allowed so all theme readers/decision points (bootstrap boundary, login/remember-me, theme switching, theme factory, xoops_getcss, and the System theme block) behave consistently with a “fail-empty then fall back to default” contract.

Changes:

  • Add shared helpers xoops_validateThemeName() and xoops_resolveThemeConfig() and require them via include/functions.php.
  • Normalize theme_set / theme_set_allowed once at the include/common.php config-merge boundary and also at key decision-point call sites.
  • Add PHPUnit coverage for the validation/normalization contract and extend pre-commit checks to prevent regressions (loose in_array / raw config assignment patterns).

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
htdocs/include/theme_config.php Introduces shared validation + resolution helpers for theme config.
htdocs/include/common.php Boundary-normalizes theme config after file+DB config merge; tightens remember-me theme handling.
htdocs/include/functions.php Requires the new helper file; hardens xoops_getcss() by validating the theme parameter.
htdocs/include/checklogin.php Validates user theme (raw 'n' read) and uses strict membership checks.
htdocs/include/site-closed.php Builds ThemeFactory config via resolver rather than raw $xoopsConfig reads.
htdocs/header.php Routes ThemeFactory configuration through the resolver for consistent invariants.
htdocs/class/theme.php Hardens request/session theme selection and makes isThemeAllowed() strict.
htdocs/class/xoopsform/formselecttheme.php Populates theme select options from the resolved allowed list.
htdocs/class/xoopskernel.php Normalizes config in the xoops_getConfigOption() cache path and validates POST/session themes with strict membership checks.
htdocs/modules/system/blocks/system_blocks.php Switches the theme-switch block to use centralized helpers rather than local closures.
.githooks/pre-commit Adds sniffs to prevent reintroducing loose in_array and raw ThemeFactory assignments from $xoopsConfig.
tests/unit/htdocs/include/ThemeConfigTest.php Adds unit tests covering the validation matrix and resolver behavior.

Comment thread htdocs/include/functions.php Outdated
// parameter and may be passed raw by future callers. Validate so a
// tainted value (path separator, null byte, ..) cannot reach the
// <link href=> attribute or filesystem checks below.
$theme = xoops_validateThemeName((string) $theme);
Comment thread htdocs/class/theme.php Outdated
// Pipe writes back to $GLOBALS['xoopsConfig'] through the
// validator so the boundary-normalised invariant set by
// common.php holds for every downstream reader.
$GLOBALS['xoopsConfig']['theme_set'] = xoops_validateThemeName((string) $options['folderName']) ?: 'default';
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.githooks/pre-commit:
- Around line 127-133: The current grep in the hits assignment is too broad and
matches local variables like $allowedThemes; update the regex used to set hits
so it only matches property writes (object->allowedThemes or
object->defaultTheme) by requiring the literal '->' before the property name,
e.g. change the pattern in the hits assignment from
'(allowedThemes|defaultTheme)[[:space:]]*=[[:space:]]*\$xoopsConfig\[' to
something like
'->[[:space:]]*(allowedThemes|defaultTheme)[[:space:]]*=[[:space:]]*\$xoopsConfig\['
so only property assignments trigger the report and the hook no longer
false-fires on local variable assignments.

In `@htdocs/include/site-closed.php`:
- Around line 42-44: The closed-site template still uses raw $xoopsConfig values
after normalizing the theme; update the assignments that set xoops_theme,
xoops_imageurl, and xoops_themecss to use the resolved $themeConfig values
returned by xoops_resolveThemeConfig (and the same $themeConfig['theme_set'] /
related keys used to set $xoopsThemeFactory->defaultTheme and ->allowedThemes)
so the template consistently references the normalized theme and image/CSS paths
when rendering the closed-site page.

In `@htdocs/include/theme_config.php`:
- Around line 2-59: Prepend the standard XOOPS copyright header block to the top
of htdocs/include/theme_config.php (so the file begins with the required project
header), then move the existing helper-specific rationale block immediately
after that header without changing its content; ensure you don't alter the
helper text or credits and keep the function_exists() guard comments intact (the
helper comments starting "Side-effect-free theme-config helpers..." should
follow the standard XOOPS header).

In `@tests/unit/htdocs/include/ThemeConfigTest.php`:
- Around line 5-7: ThemeConfigTest uses PHPUnit 9.6-incompatible #[Test]
attributes and non-standard method names (validate..., resolve...) so tests are
not discovered; rename each test method in class ThemeConfigTest to start with
test (e.g., testValidateSomething, testResolveSomething) and replace attribute
annotations (#[CoversFunction], #[Test]) with equivalent docblock tags (`@covers`,
`@test`) above the methods to preserve coverage metadata and restore discovery
across PHPUnit versions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ef5367d5-b640-4b0e-b68e-22713ff11c65

📥 Commits

Reviewing files that changed from the base of the PR and between 87e6d72 and bf19863.

📒 Files selected for processing (12)
  • .githooks/pre-commit
  • htdocs/class/theme.php
  • htdocs/class/xoopsform/formselecttheme.php
  • htdocs/class/xoopskernel.php
  • htdocs/header.php
  • htdocs/include/checklogin.php
  • htdocs/include/common.php
  • htdocs/include/functions.php
  • htdocs/include/site-closed.php
  • htdocs/include/theme_config.php
  • htdocs/modules/system/blocks/system_blocks.php
  • tests/unit/htdocs/include/ThemeConfigTest.php

Comment thread .githooks/pre-commit
Comment thread htdocs/include/site-closed.php
Comment on lines +2 to +59
/**
* Side-effect-free theme-config helpers.
*
* Single source of truth for normalising the two theme entries in
* $xoopsConfig:
*
* - xoops_validateThemeName() — fail-empty validation of one theme
* directory name (path + HTML safety)
* - xoops_resolveThemeConfig() — returns a normalised pair
* ['theme_set' => string,
* 'theme_set_allowed' => list<string>]
*
* Why this file exists: prior to issue #45, only the System theme-switch
* block (b_system_themes_show) normalised these values; every other
* runtime reader trusted the raw config entries. A corrupted xoops_config
* row (mid-upgrade state, direct override in xoopsconfig.php, manual
* edit) could feed unvalidated strings straight into <link href=>
* attributes, theme-factory folder paths, and in_array() membership
* checks. Consolidating the normalisation here lets common.php apply it
* once at the config-merge boundary and lets every decision-point caller
* (login, remember-me, theme selector, factory) reach the same answer.
*
* Validation contract: fail-empty. xoops_validateThemeName() returns ''
* for any rejected input — never raises, never sanitises by stripping.
* XOOPS theme discovery (XoopsLists::getDirListAsArray) and the theme
* factory accept arbitrary directory names including spaces and
* non-ASCII characters, so the validator does NOT enforce a
* conservative [A-Za-z0-9_.-] alphabet (which would silently drop
* legitimate `My Theme` / `テーマ` directories). Instead two safety
* classes are enforced:
*
* - Path safety: reject empty, leading dot, path separators (/, \),
* null bytes, and `..` appearing as a path segment.
* - HTML safety: reject the five HTML metacharacters (< > & " ').
* No legitimate theme directory contains those, and rejecting them
* at the boundary lets the option label and screenshot URL be
* embedded without per-renderer escaping logic.
*
* Each function is wrapped in a function_exists() guard so the file is
* safe to require_once multiple times across mixed load orders (the
* helpers are referenced from include/common.php before functions.php
* has finished loading on some preload paths).
*
* You may not change or alter any portion of this comment or credits
* of supporting developers from this source code or any supporting source code
* which is considered copyrighted (c) material of the original comment or credit authors.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* @category kernel
* @package core
* @author XOOPS Development Team
* @copyright (c) 2000-2026 XOOPS Project (https://xoops.org)
* @license GNU GPL 2 (https://www.gnu.org/licenses/gpl-2.0.html)
* @link https://xoops.org
* @since 2.7.0
*/
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Restore the standard XOOPS header block at the top of the file.

This new file starts with a custom helper rationale block, so it does not begin with the required XOOPS copyright header block. Please move the project-standard header to the top and place the helper-specific commentary after it.

As per coding guidelines: Every source file begins with the XOOPS copyright header block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@htdocs/include/theme_config.php` around lines 2 - 59, Prepend the standard
XOOPS copyright header block to the top of htdocs/include/theme_config.php (so
the file begins with the required project header), then move the existing
helper-specific rationale block immediately after that header without changing
its content; ensure you don't alter the helper text or credits and keep the
function_exists() guard comments intact (the helper comments starting
"Side-effect-free theme-config helpers..." should follow the standard XOOPS
header).

Comment on lines +5 to +7
use PHPUnit\Framework\Attributes\CoversFunction;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Which PHPUnit versions support the #[Test]and#[CoversFunction] attributes, and are they available in PHPUnit 9.6?

💡 Result:

PHPUnit versions 10.0 and later support the #[Test] and #[CoversFunction] attributes [1][2]. These attributes were introduced to replace the legacy docblock annotations as part of the transition to using native PHP 8 attributes for metadata [1][2]. PHPUnit 9.6 does not support the #[Test] and #[CoversFunction] attributes [3][2]. In version 9.6, developers must continue using the corresponding docblock annotations (e.g., @test and @covers) [3][2]. The ability to use PHP 8 attributes for test metadata was a primary feature introduced starting with PHPUnit 10 [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="tests/unit/htdocs/include/ThemeConfigTest.php"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

echo "== $FILE (head) =="
head -n 120 "$FILE"
echo
echo "== $FILE (tail) =="
tail -n 120 "$FILE"
echo
echo "== public functions in $FILE =="
rg -n "public function" "$FILE" || true
echo
echo "== attribute usage in $FILE =="
rg -n "#\[" "$FILE" || true

Repository: XOOPS/XoopsCore27

Length of output: 9992


Fix PHPUnit 9.6 test discovery in tests/unit/htdocs/include/ThemeConfigTest.php

ThemeConfigTest relies on #[Test] + method names like validate.../resolve.... PHPUnit 9.6 doesn’t support these PHPUnit attribute annotations, so the tests won’t be discovered. Rename methods to test{MethodName} (and switch #[CoversFunction]/#[Test] to docblock @covers/@test if you want consistent coverage metadata across versions).

🔧 Minimal compatible pattern
-use PHPUnit\Framework\Attributes\CoversFunction;
-use PHPUnit\Framework\Attributes\Test;
 use PHPUnit\Framework\TestCase;
@@
-#[CoversFunction('xoops_validateThemeName')]
-#[CoversFunction('xoops_resolveThemeConfig')]
+/**
+ * `@covers` ::xoops_validateThemeName
+ * `@covers` ::xoops_resolveThemeConfig
+ */
 class ThemeConfigTest extends TestCase
 {
-    #[Test]
-    public function validateRejectsEmptyString(): void
+    public function testValidateRejectsEmptyString(): void
     {
         $this->assertSame('', xoops_validateThemeName(''));
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/htdocs/include/ThemeConfigTest.php` around lines 5 - 7,
ThemeConfigTest uses PHPUnit 9.6-incompatible #[Test] attributes and
non-standard method names (validate..., resolve...) so tests are not discovered;
rename each test method in class ThemeConfigTest to start with test (e.g.,
testValidateSomething, testResolveSomething) and replace attribute annotations
(#[CoversFunction], #[Test]) with equivalent docblock tags (`@covers`, `@test`)
above the methods to preserve coverage metadata and restore discovery across
PHPUnit versions.

Follow-up on bf19863 (XOOPS#81). Nine review findings, all in-scope for
the same issue XOOPS#45 consolidation.

Codex review (three rounds):

  1. themeSelect() ignored file-config overrides.
     xos_kernel_Xoops2::themeSelect() resolved exclusively from
     xoops_getConfigOption(), which reads the XoopsConfigHandler cache.
     var/configs/xoopsconfig.php overrides never reach that cache, so a
     site overriding theme_set_allowed at the file level could see the
     theme switcher reject themes the rest of the runtime accepts.
     Prefer the already-merged $GLOBALS['xoopsConfig']; fall back to
     xoops_getConfigOption() only when the global is unset (partial-init
     paths that may run before common.php hydrates).

  2. createInstance() could still use an unsafe path segment.
     The previous patch validated the write back to
     $GLOBALS['xoopsConfig']['theme_set'] but $testPath was built from
     the unvalidated $options['folderName']. Two paths skip the inner
     resolution block:
       - A non-standard caller can pass a poisoned folderName directly.
       - $this->defaultTheme is a public property; any caller can
         overwrite it before createInstance() runs.
     Add an unconditional validate-with-fallback after the resolution
     block, so $testPath cannot use a tainted segment regardless of
     how folderName reached this point.

  3. xoops_getcss() emitted a conversion warning on non-scalar input.
     The unconditional (string) cast warned on array / object / resource
     under PHP 8.x. Skip the cast on non-scalar input — the helper
     reaches the boundary-normalised fallback in that case anyway.

  4. xoops_resolveThemeConfig() handled scalar allowed but not scalar
     current. The allowed-list branch already used is_scalar(); the
     current-theme branch used is_string(), which silently rejected a
     legitimate int 0 (legacy xoops_config rows for the all-numeric
     theme directory "0"). Make the two paths consistent — is_scalar
     for both, with non-scalar (array / object / resource) still
     rejected. Adds a test that an int 0 theme_set survives as the
     string "0".

  5. Pre-commit sniff 11 was too broad.
     The raw-assignment regex matched any variable named allowedThemes
     or defaultTheme, including local variables that are downstream of
     a legitimate resolver call (e.g. $allowedThemes = $themeConfig[…]
     reading a key that happens to be theme_set_allowed). Require the
     `->` property operator so only property writes trigger the report.
     Add `--` to the grep call so the leading `->` isn't parsed as an
     option flag.

  6. Theme name "0" was dropped by Elvis fallbacks (R-063).
     The createInstance() validate-with-fallback added in (2) used
     `xoops_validateThemeName($x) ?: $fallback` and PHP treats the
     string '0' as falsy, so a perfectly valid all-numeric theme
     directory was silently replaced by $fallback. Two more spots
     had the same shape via empty():
       - empty($options['folderName']) at the start of createInstance()
         treated '0' as missing and re-resolved it.
       - empty($theme_set) in XoopsTpl::setCompileId() swapped a '0'
         theme name for $xoopsConfig['theme_set'] when building the
         compile-id.
     Replace all four with explicit === '' checks (theme.php twice,
     setCompileId once, plus the inner-block elseif at line 82). The
     other empty() calls in setCompileId (template_set, module_dirname)
     are untouched — out of scope for issue XOOPS#45.

  9. Unguarded (string) casts on potentially non-scalar values.
     Four call sites still cast session payloads, public properties,
     and raw $options entries to string before validation:
       - theme.php session-trust branch ($_SESSION[...]['defaultTheme']).
       - theme.php $this->defaultTheme.
       - theme.php $options['folderName'].
       - xoopskernel.php $_SESSION['xoopsUserTheme'].
     `(string) ['x']` emits a conversion Warning under PHP 8.x AND
     returns the literal string "Array", which then passes the
     path-safety check in xoops_validateThemeName() and reaches the
     theme path logic. Introduce a third helper
     xoops_validateThemeValue() in theme_config.php that bundles the
     is_scalar gate + xoops_validateThemeName() — the established
     pattern from xoops_getcss() and xoops_resolveThemeConfig() —
     and route every untrusted-source call site through it.
     xoops_resolveThemeConfig() current-theme branch and allowed-list
     array_map also collapse onto the new helper so the validation
     contract is identical everywhere. Adds tests covering scalar
     coercion (int, float, bool), non-scalar rejection (array, object,
     null), and path-safety pass-through on coerced values; smoke-test
     against a custom error handler confirms zero PHP warnings on any
     non-scalar input.

CodeRabbit review:

  7. site-closed.php template assigns used raw $xoopsConfig reads.
     The factory assignments above were updated to use the resolved
     $themeConfig; the template assigns below still read $xoopsConfig
     directly. They are safe-by-invariant from the boundary normalise,
     but reading the actual post-factory state matches the "theme not
     found" fallback in createInstance (which writes 'default' back to
     $GLOBALS['xoopsConfig']['theme_set'] when theme.tpl / theme.html
     is absent). Re-resolve after createInstance and use the post-
     factory pair for the template; the global is then read in exactly
     one place.

Scrutinizer:

  8. PHPDoc generics syntax. `list<string>` is a PHPStan/Psalm-only
     shape; Scrutinizer's PHPDoc parser raises "Documentation Bug" on
     it. Replace with `string[]` — universally parsed, PHPStan still
     accepts it as an alias for array<int|string, string>. The function
     still returns array_values(...) at runtime so the list shape is
     preserved at the value level even if the type shape is widened.

Not addressed:

  - The PHPUnit 9.6 attribute claim. This repo runs PHPUnit ^11.2
    (tests/phpunit.xml.dist line 3) and uses #[Test] / #[CoversClass]
    by convention. CI is green across PHP 8.2-8.5.
  - The XOOPS header-order point on theme_config.php. The file matches
    the established include/file_safety.php pattern (summary, rationale,
    BSD notice, @category/@package/@author/@copyright/@license/@link),
    so reordering would diverge from the in-tree convention.

Refs:
  - PR XOOPS#81 review comments by Codex (three passes), CodeRabbit, Scrutinizer.
  - R-063 in base.md — Elvis-drops-zero, the rule violated and now
    re-fixed here.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment on lines +5 to +25
use PHPUnit\Framework\Attributes\CoversFunction;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

require_once XOOPS_ROOT_PATH . '/include/theme_config.php';

/**
* Coverage for xoops_validateThemeName() and xoops_resolveThemeConfig()
* in htdocs/include/theme_config.php. The helpers consolidate the
* defensive normalisation previously inlined in
* b_system_themes_show() so that every runtime reader of
* theme_set / theme_set_allowed shares one source of truth.
*
* Validation contract is fail-empty: invalid names return '', invalid
* config entries are dropped, current theme always survives in the
* allowed list, and a totally-corrupted config falls back to 'default'.
*/
#[CoversFunction('xoops_validateThemeName')]
#[CoversFunction('xoops_validateThemeValue')]
#[CoversFunction('xoops_resolveThemeConfig')]
class ThemeConfigTest extends TestCase
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.githooks/pre-commit (1)

118-125: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Loose-in_array sniff: [^)]* makes it a leaky heuristic, but acceptable.

The body class [^)]* stops at the first ), so a call like in_array(getTheme(), $cfg['theme_set_allowed']) won't match because of getTheme()'s closing paren. Likewise the strict-mode exclusion , true) is line-wide, so a second strict in_array(...) on the same line can mask a loose first one. Both are false-negatives, not false-positives — the hook won't block clean commits — so this is fine as a best-effort sniff. Worth a one-line note that multi-statement lines may slip through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.githooks/pre-commit around lines 118 - 125, The current loose in_array
sniff (the hits variable using grep -E
"in_array[[:space:]]*\([^)]*(theme_set_allowed)" and the strict exclusion grep
-vE ",[[:space:]]*true[[:space:]]*\)") can miss calls when the argument contains
nested parentheses or when multiple in_array calls share a line; add a concise
one-line comment above the hits assignment explaining this limitation (that the
regex stops at the first ')' and multi-statement lines can produce
false-negatives) and reference the symbols in_array, theme_set_allowed, hits and
report so future maintainers know why the heuristic is imperfect and why it
remains acceptable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.githooks/pre-commit:
- Around line 118-125: The current loose in_array sniff (the hits variable using
grep -E "in_array[[:space:]]*\([^)]*(theme_set_allowed)" and the strict
exclusion grep -vE ",[[:space:]]*true[[:space:]]*\)") can miss calls when the
argument contains nested parentheses or when multiple in_array calls share a
line; add a concise one-line comment above the hits assignment explaining this
limitation (that the regex stops at the first ')' and multi-statement lines can
produce false-negatives) and reference the symbols in_array, theme_set_allowed,
hits and report so future maintainers know why the heuristic is imperfect and
why it remains acceptable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e4f64f21-44e9-49c9-9b75-6d8c5075d7d5

📥 Commits

Reviewing files that changed from the base of the PR and between bf19863 and 88813ff.

📒 Files selected for processing (8)
  • .githooks/pre-commit
  • htdocs/class/template.php
  • htdocs/class/theme.php
  • htdocs/class/xoopskernel.php
  • htdocs/include/functions.php
  • htdocs/include/site-closed.php
  • htdocs/include/theme_config.php
  • tests/unit/htdocs/include/ThemeConfigTest.php

@mambax7 mambax7 merged commit b65dfb6 into XOOPS:master May 29, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Consolidate theme-config normalisation across runtime callers

2 participants