Unit/System Tests bug PR-6.2-dev Pending

User tests: Successful: Unsuccessful:

avatar bhuvan-somisetty
bhuvan-somisetty
20 Aug 2026

Pull Request resolves #48281.

  • I read the Generative AI policy and my contribution is either not created with the help of AI or is compatible with the policy and GNU/GPL 2 or later.

Summary of Changes

This PR fixes two bugs in Joomla\CMS\Table\ContentHistory::getSha1():

  1. Prevent caller object in-place mutation: When $jsonData is passed as an object (e.g., from Versioning::store()), $object is now decoupled before unsetting ignored properties (modified, modified_by, checked_out, version, hits, path). This prevents stripping properties from the caller's live in-memory Table/data instance.
  2. Correct nested sub-property normalization: Fixed the inner loop in getSha1() to assign normalized values to $value->$subName (on the nested object) instead of $object->$subName. This prevents flattening nested JSON column properties onto the root object, avoids clobbering top-level properties (like title or alias) when nested keys collide, and ensures nested JSON column values are normalized.
  3. Safety for non-object payloads: Added a guard returning '' when payload cannot be decoded into an object.
  4. Unit Tests: Added tests/Unit/Libraries/Cms/Table/ContentHistoryTest.php covering input immutability, nested property normalization, key collision safety, and deterministic hash calculation.

Testing Instructions

Run the unit tests:

libraries/vendor/bin/phpunit tests/Unit/Libraries/Cms/Table/ContentHistoryTest.php

Or test programmatically with a sample object containing nested parameters:

$data = (object) [
    'title'       => 'Article Title',
    'modified_by' => 42,
    'version'     => 1,
    'params'      => (object) [
        'title'      => 'Param Header',
        'show_title' => 1,
    ],
];

$historyTable = new \Joomla\CMS\Table\ContentHistory($db);
$contentType  = new \Joomla\CMS\Table\ContentType($db);
$hash         = $historyTable->getSha1($data, $contentType);

Actual result BEFORE applying this Pull Request

  1. $data->modified_by and $data->version are removed from the caller's live object $data.
  2. $data->title is overwritten with 'Param Header'.
  3. $data->show_title is injected into the root object $data.
  4. $data->params->show_title remains unconverted integer 1 instead of normalized string '1'.

Expected result AFTER applying this Pull Request

  1. $data remains completely unmodified in-place ($data->modified_by and $data->version remain intact).
  2. $data->title is preserved as 'Article Title'.
  3. Root object is not polluted with sub-properties.
  4. Nested properties are normalized in the calculated hash.
  5. All unit tests pass.

Link to documentations

Please select:

  • Documentation link for guide.joomla.org:

  • No documentation changes for guide.joomla.org needed

  • Pull Request link for manual.joomla.org:

  • No documentation changes for manual.joomla.org needed

Signed-off-by: bhuvan-somisetty somisettybhuvan5@gmail.com

avatar bhuvan-somisetty bhuvan-somisetty - open - 20 Aug 2026
avatar bhuvan-somisetty bhuvan-somisetty - change - 20 Aug 2026
Status New ⇒ Pending
avatar joomla-cms-bot joomla-cms-bot - change - 20 Aug 2026
Category ⇒ Libraries Unit Tests
avatar bhuvan-somisetty bhuvan-somisetty - change - 20 Aug 2026
Labels Added: Unit/System Tests PR-5.4-dev
avatar brianteeman
brianteeman - comment - 24 Aug 2026

I suspect that this is an AI generated PR

Please explain how you can set a title in the param

avatar brianteeman
brianteeman - comment - 24 Aug 2026
avatar bhuvan-somisetty
bhuvan-somisetty - comment - 11 Sep 2026

@brianteeman Fair question!

In the snippet in the testing instructions, 'title' => 'Param Header' inside params was just an artificial mock property used in the unit test to verify what happens if a nested key shares the same name as a top-level column. In real Joomla articles, params of course stores options like show_title, show_author, link_titles, etc., while other JSON columns like images and urls store their own properties (image_intro, float_intro, urla).

The reason this matters in ContentHistory::getSha1() is the loop at line 138:

foreach ($object as $name => $value) {
    if (\is_object($value)) {
        // Go one level down for JSON column values
        foreach ($value as $subName => $subValue) {
            $object->$subName = \is_int($subValue) || \is_bool($subValue) || $subValue === null ? (string) $subValue : $subValue;
        }
    } ...
}

Notice it assigns to $object->$subName instead of $value->$subName.

For every standard article with params (or images/metadata), this means:

  1. Every sub-field like show_title or image_intro gets copied onto the root object ($object->show_title), polluting the top level.
  2. The property inside $object->params->show_title is never normalized to a string in the nested object where it belongs.
  3. If any nested property in a custom component, module, or extension using Content History ever shared a key name with a column on #__content, the root property would be silently overwritten during hash calculation.

Changing that line to $value->$subName ensures the sub-properties are normalized within their actual container ($object->params, $object->images), and decoupling $object ensures unset($object->$remove) doesn't strip modified_by/version from the caller's live table instance.

Hope that makes the intent and the test case clearer!

avatar bhuvan-somisetty
bhuvan-somisetty - comment - 11 Sep 2026

@brianteeman That was just a dummy property in the unit test data to test name collision with root fields, not a real Joomla article param.

The actual issue is at line 138 in ContentHistory.php where $object->$subName = ... writes to the root object instead of $value->$subName = .... So for any article with params or images, it was dumping all the nested properties (like show_title or image_intro) directly onto the root $object.

avatar richard67 richard67 - change - 30 Oct 2026
Title
…
[5.4] Fix ContentHistory::getSha1 object mutation and nested property flattening
[6.2] Fix ContentHistory::getSha1 object mutation and nested property flattening
avatar richard67 richard67 - edited - 30 Oct 2026
avatar richard67 richard67 - change - 30 Oct 2026
Labels Added: bug PR-6.2-dev
Removed: PR-5.4-dev
avatar richard67
richard67 - comment - 30 Oct 2026

As 5.4 has reached end of bugfix support and is now in security only support mode, I've allowed myself to rebase this PR here to 6.2-dev, where bug fixes should be made now. This worked cleanly, so the PR is still ok so far, it just needs 2 successful human tests.

avatar bhuvan-somisetty
bhuvan-somisetty - comment - 30 Oct 2026

Thanks @richard67 for rebasing it to 6.2-dev! Appreciate the help.

avatar brianteeman
brianteeman - comment - 13 Nov 2026

Why are we using json_encode() followed by json_decode() to copy the object? Would cloning the object and its nested objects not achieve the same result without the overhead of serialising everything to JSON?

Add a Comment

Login with GitHub to post a comment