No Code Attached Yet bug Webservices
avatar romanwolf-git
romanwolf-git
7 May 2026

Steps to reproduce the issue

Send a partial PATCH to /api/index.php/v1/users/levels/{id} that omits rules — e.g. renaming a level:

PATCH /api/index.php/v1/users/levels/377
Authorization: Bearer <token>
Content-Type: application/json

{"title": "Global-Country-LATAM-MX-ESS"}

Before the call: level 377 has rules = [8, 1535].

Expected result

Per JSON:API merge semantics (RFC 7396-style), only title should change; rules should be preserved.

Actual result

HTTP 200, response body shows rules = [0]. The previous rules array is destroyed silently. Recovery requires a follow-up PATCH with the full attribute set.

System information

  • Joomla 4.4.14 (4.4 LTS; mechanism trace verified against 4.4-dev source on github)
  • API access via Bearer token, JSON body

Mechanism (root cause trace)

  1. ApiController::save() does try to merge: when method is PATCH, it loads the existing record and copies columns from $table into $data for any columns missing from the request. The #__viewlevels rules column is included.

  2. The merged $data['rules'] is the JSON-encoded string stored in the DB (e.g. "[8,1535]").

  3. LevelModel::validate($form, $data) runs the payload through the level form schema. The rules field has filter="intarray". IntarrayFilter::filter is coercive, not rejecting:

    $value = \is_array($value) ? $value : [$value];
    $value = ArrayHelper::toInteger($value);

    So "[8,1535]" becomes ["[8,1535]"] becomes [0] (cast-to-int of a string starting with [ returns 0).

  4. LevelModel::save($data) receives $data['rules'] = [0] and passes it through to the table.

  5. Joomla\CMS\Table\ViewLevel::bind() JSON-encodes [0] and stores it; readback returns [0].

The destructive behavior fires for any PATCH that omits rules, regardless of which other attributes are being changed. The client never has the chance to "send the right thing" because it didn't send rules at all — Joomla itself injects the JSON string into $data['rules'] during the table-merge step.

Sister bug

Same shape as #42229 (article tags lost on PATCH) — non-DB-column form field gets a coercive filter applied to a value the controller pulled from the table, with destructive results.

Suggested fix

The asymmetry: Table\ViewLevel::bind() JSON-encodes arrays going in, but nothing JSON-decodes the column on read inside the controller's PATCH-merge — ApiController::save() reads $table->{$field->Field} directly post-load(), where rules is still the raw varchar. The string then hits IntarrayFilter which can't recognize a JSON-array string and coerces it to [0].

Possible fixes:

  • (a) ApiController::save() skips the table-merge for fields whose form filter is array-typed and whose DB column is JSON-encoded;
  • (b) LevelModel overrides the merge step or getItem-decodes the value before validate();
  • (c) IntarrayFilter detects and json_decodes JSON-array strings before wrapping.

Workaround

Application-level: GET the level first, merge the partial payload into the full attribute set, then PATCH the union. Reference implementation: https://github.com/sunwizglobal/joomla-api-client/pull/18

avatar romanwolf-git romanwolf-git - open - 7 May 2026
avatar joomla-cms-bot joomla-cms-bot - change - 7 May 2026
Labels Added: No Code Attached Yet
avatar joomla-cms-bot joomla-cms-bot - labeled - 7 May 2026
avatar richard67
richard67 - comment - 7 May 2026

Joomla 5.3.3 (verified against 4.4-dev source for the trace below)

Not related to the issue, but if you are still on 5.3.3 you have a serious security problem (unless that site is located in an intranet completely isolated from the internet).

avatar romanwolf-git romanwolf-git - change - 8 May 2026
The description was changed
avatar romanwolf-git romanwolf-git - edited - 8 May 2026
avatar romanwolf-git
romanwolf-git - comment - 8 May 2026

Two corrections to my issue text:

  1. Version was wrong. I conflated the version pre-existing #45971 was filed against with our actual install. The site is on 4.4.14, not 5.3.3. Issue body edited.

  2. Mechanism step 3 was wrong. I described IntarrayFilter as rejecting the JSON string from validData; reading libraries/src/Form/Filter/IntarrayFilter.php it's actually coerciveis_array($value) ? $value : [$value] then ArrayHelper::toInteger(), so "[8,1535]" becomes ["[8,1535]"] becomes [0]. Same destructive end-state, slightly different path. Issue body updated.

To head off any read-across from #45971's "clients should send arrays" framing — this bug is not a client-payload issue. It fires on PATCHes that omit rules entirely, e.g. {"title":"X"}. Joomla itself, not the client, injects the JSON string into $data['rules'] during ApiController::save()'s PATCH table-merge. So filter="intarray" is correct in isolation for the form, but the API PATCH path needs one of:

  • (a) ApiController::save() skipping the table-merge for fields whose form filter is array-typed and whose DB column is JSON;
  • (b) LevelModel decoding the merged value before validate();
  • (c) IntarrayFilter detecting and json_decodeing JSON-array strings.

Same shape as #42229.

avatar richard67
richard67 - comment - 8 May 2026

4.4.14???That is end of live since last year.

avatar alikon alikon - change - 10 May 2026
Labels Added: Webservices
avatar alikon alikon - labeled - 10 May 2026
avatar alikon
alikon - comment - 10 May 2026

it happens on 6.1 as well

avatar alikon alikon - change - 10 May 2026
Labels Added: bug
avatar alikon alikon - labeled - 10 May 2026
avatar alikon
alikon - comment - 10 May 2026

pleas test #47751

Add a Comment

Login with GitHub to post a comment