Skip to content

fix(UserConfig): cast getTypedValue() result to string in getValueBool() to fix PHP 8.4 TypeError#59630

Open
thereisnotime wants to merge 1 commit intonextcloud:stable33from
thereisnotime:fix/userconfig-getvaluebool-strtolower-type-error
Open

fix(UserConfig): cast getTypedValue() result to string in getValueBool() to fix PHP 8.4 TypeError#59630
thereisnotime wants to merge 1 commit intonextcloud:stable33from
thereisnotime:fix/userconfig-getvaluebool-strtolower-type-error

Conversation

@thereisnotime
Copy link
Copy Markdown

Summary

Fixes #59629

PHP 8.4 made passing non-strings to strtolower() a fatal TypeError (previously a deprecation). UserConfig::getValueBool() calls strtolower() directly on the return value of getTypedValue(), which can return a non-string when stored value type metadata doesn't align with ValueType::BOOL.

In practice this manifests when user_ldap calls getValueBool($uid, 'user_ldap', 'isDeleted') — the stored isDeleted preference has no type metadata, causing getTypedValue() to return a non-string, which then breaks strtolower() in PHP 8.4.

Change

// Before
$b = strtolower($this->getTypedValue($userId, $app, $key, $default ? 'true' : 'false', $lazy, ValueType::BOOL));

// After
$b = strtolower((string)$this->getTypedValue($userId, $app, $key, $default ? 'true' : 'false', $lazy, ValueType::BOOL));

One-character fix: add (string) cast before the strtolower() call. This is safe and defensive — getTypedValue() is already declared to return string, so this is a no-op in the happy path and a guard for edge cases.

Impact

Without this fix, on PHP 8.4 + NC 33 with user_ldap enabled:

  • Every LDAP user request throws TypeError: strtolower(): Argument #1 ($string) must be of type string, string given
  • Desktop clients cannot authenticate and prompt for re-login
  • The web UI returns 500 Internal Server Error for LDAP users

Related

PHP 8.4 made passing non-strings to strtolower() a fatal TypeError.
getTypedValue() can return a non-string when stored value type metadata
doesn't align with ValueType::BOOL (e.g. user_ldap isDeleted preference).
The (string) cast ensures strtolower() always receives a valid string.

Fixes nextcloud#59629
@thereisnotime thereisnotime requested a review from a team as a code owner April 14, 2026 14:45
@thereisnotime thereisnotime requested review from ArtificialOwl, artonge, icewind1991 and salmart-dev and removed request for a team April 14, 2026 14:45
@artonge artonge requested a review from come-nc April 14, 2026 15:31
@artonge
Copy link
Copy Markdown
Collaborator

artonge commented Apr 14, 2026

causing getTypedValue() to return a non-string

But getTypedValue() is typed, so if it was returning a non-string, then PHP would throw, no?

From your bug report:

TypeError: strtolower(): Argument #1 ($string) **must be of type string, string given**
  at /var/www/html/lib/private/Config/UserConfig.php:688

I am confused, "string given", then sounds like it is the correct type. Am I missing something? I don't get why strtolower is complaining.

@thereisnotime
Copy link
Copy Markdown
Author

Good catch on the confusing error message — you're right to question it.

The file has declare(strict_types=1). In PHP 8.4, when getTypedValue() (declared as returning string) returns a value that violates its own return type, the TypeError is thrown at the call site (line 688) rather than inside getTypedValue(). PHP then attributes the error to the outermost function on that expression — strtolower() — which is why the message reads strtolower(): Argument #1 ($string) must be of type string, string given even though strtolower itself never got the chance to run.

The (string) cast causes PHP to evaluate and coerce the return value before attempting to pass it to strtolower, which prevents the TypeError at line 688 regardless of what getTypedValue() actually returned.

We've verified this fix in production on NC 33.0.2 / PHP 8.4.

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.

2 participants