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].
Per JSON:API merge semantics (RFC 7396-style), only title should change; rules should be preserved.
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.
4.4-dev source on github)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.
The merged $data['rules'] is the JSON-encoded string stored in the DB (e.g. "[8,1535]").
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).
LevelModel::save($data) receives $data['rules'] = [0] and passes it through to the table.
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.
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.
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:
ApiController::save() skips the table-merge for fields whose form filter is array-typed and whose DB column is JSON-encoded;LevelModel overrides the merge step or getItem-decodes the value before validate();IntarrayFilter detects and json_decodes JSON-array strings before wrapping.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
| Labels |
Added:
No Code Attached Yet
|
||
Two corrections to my issue text:
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.
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 coercive — is_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:
ApiController::save() skipping the table-merge for fields whose form filter is array-typed and whose DB column is JSON;LevelModel decoding the merged value before validate();IntarrayFilter detecting and json_decodeing JSON-array strings.Same shape as #42229.
4.4.14???That is end of live since last year.
| Labels |
Added:
Webservices
|
||
it happens on 6.1 as well
| Labels |
Added:
bug
|
||
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).