PR-5.4-dev Pending

User tests: Successful: Unsuccessful:

avatar wakqasahmed
wakqasahmed
12 Sep 2026

Pull Request resolves #48034.

  • 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

updateManifestCaches() in administrator/components/com_admin/script.php queries every core extension (by type/element/folder/client_id) and calls Installer::refreshManifestCache() on each, reporting a "Error on updating manifest cache" message for any that return false.

Installer::refreshManifestCache() deliberately aborts and returns false when an extension's state == -1 (its own translated message, JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE, exists specifically for this) — that's intentional, not a real failure. But updateManifestCaches() has no way to tell an intentional no-op from a genuine problem, so any core extension that ends up at state = -1 (as the reporter saw for phpass and plg_quickicon_eos after a 3.x → 6.1.1 migration) always surfaces a spurious error during every subsequent update or reinstall.

Added state != -1 to the query's per-extension WHERE clause, so disabled/pending-removal core extensions are excluded from the refresh attempt entirely instead of being attempted and then reported as failed. This doesn't touch Installer::refreshManifestCache() itself or try to address why a core extension ends up at state=-1 after a migration — that's a separate question about the upgrade path; this only stops a known no-op from being reported as an error.

Testing Instructions

No existing test harness covers this script (it's a legacy install/update hook with no unit tests, and a full behavioral test would need a DB-backed core-update simulation). Verified manually: with the query's OR-joined per-extension clauses, AND state != -1 is appended inside each extension's own AND-chain, so it's scoped correctly per extension regardless of the other extensions in the list (SQL AND binds tighter than OR, and Joomla's QueryElement doesn't wrap each where() call in its own parentheses, so this only works because of that precedence — confirmed by reading Joomla\Database\Query\QueryElement::__toString()). Also confirmed against a fixture list of (extension_id, state) pairs that state=-1 rows are excluded from the result set while state 0/1 rows remain.

Actual result BEFORE applying this Pull Request

Any core extension sitting at state = -1 causes updateManifestCaches() to report an "Error on updating manifest cache" during every subsequent Joomla core update or reinstall, even though the extension was correctly and intentionally skipped.

Expected result AFTER applying this Pull Request

Core extensions with state = -1 are excluded from the refresh query entirely, so they no longer produce a spurious error during updates.

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

avatar wakqasahmed wakqasahmed - open - 12 Sep 2026
avatar wakqasahmed wakqasahmed - change - 12 Sep 2026
Status New Pending
avatar joomla-cms-bot joomla-cms-bot - change - 12 Sep 2026
Category Administration com_admin
avatar richard67
richard67 - comment - 12 Sep 2026

Testing Instructions

No existing test harness covers this script (it's a legacy install/update hook with no unit tests, and a full behavioral test would need a DB-backed core-update simulation).

@wakqasahmed A code review is definitely not a sufficient test. It needs to do a real test with a cure update.

Things which are relevant for end users have to be tested with real human tests.

In addition, we require that contributors have tested their PRs themselves before submitting. Your testing instructions read as if you have not done such a real test and relied on code review (with help of AI). So this requirements seems not to be fulfilled either.

Please provide useful testing instructions for end users for other testers and test your changes with these instructions, too.

Thanks in advance.

avatar wakqasahmed
wakqasahmed - comment - 13 Sep 2026

I have now tested this with a real Joomla installation and complete Joomla core updates, rather than relying on code review.

Environment tested

  • Joomla base: current 5.4-dev, commit 0ec52b315b
  • PR package: 5.4.9-dev+pr.48446, commit e4daeff3f0
  • PHP 8.3.31
  • MariaDB 11.4.13
  • Fresh Joomla installation created with Joomla's supported CLI installer
  • Frontend and administrator both returned HTTP 200 after the test

Testing instructions

Use a disposable Joomla 5.4-dev installation or two identical copies of one installation. Replace j54_ below with your site's database prefix.

  1. In phpMyAdmin or another database tool, confirm the core rows exist:
SELECT extension_id, name, type, element, folder, client_id, state
FROM j54_extensions
WHERE type = 'library'
  AND element IN ('joomla', 'phpass')
ORDER BY extension_id;
  1. Simulate the migration artifact and add a happy-path marker:
UPDATE j54_extensions
SET state = -1,
    manifest_cache = 'DISABLED_SENTINEL'
WHERE type = 'library'
  AND element = 'phpass'
  AND folder = ''
  AND client_id = 0;

UPDATE j54_extensions
SET manifest_cache = 'ACTIVE_SENTINEL'
WHERE type = 'library'
  AND element = 'joomla'
  AND folder = ''
  AND client_id = 0;
  1. Before applying this PR, perform a Joomla core update or reinstall.

For a reproducible 5.4-dev control, I used Joomla CI's unrelated PR 48450 package, which does not contain this fix:

php cli/joomla.php core:update:channel custom \
  --url='https://artifacts.joomla.org/drone/joomla/joomla-cms/5.4-dev/48450/downloads/96430/pr_list.xml' \
  --no-interaction

php cli/joomla.php core:update:check --no-interaction
php cli/joomla.php core:update --no-interaction --no-ansi

Alternatively, use System → Update → Joomla with that custom update URL.

  1. Before the PR, the update finishes with errors. My exact result was:
[ERROR] Update finished with errors. Please check logs for details.

The update log contained:

Refresh Manifest Cache failed: lib_phpass Extension is not currently installed.
An error has occurred while running "JoomlaInstallerScript::updateManifestCaches". Code: 0. Message: Error on updating manifest cache: (type, element, folder, client) = (library, phpass, lib_phpass, 0).
  1. Restore the original disposable installation/database snapshot, or repeat on the second identical installation. Run the two SQL UPDATE statements again.

  2. Configure Joomla to use this PR's official update package:

php cli/joomla.php core:update:channel custom \
  --url='https://artifacts.joomla.org/drone/joomla/joomla-cms/5.4-dev/48446/downloads/96422/pr_list.xml' \
  --no-interaction

php cli/joomla.php core:update:check --no-interaction
php cli/joomla.php core:update --no-interaction --no-ansi

The same operation can be run from System → Update → Joomla after setting the custom update URL in the Joomla Update options.

  1. Expected result with this PR:
[OK] Joomla core updated successfully!

There should be no updateManifestCaches/lib_phpass error.

  1. Confirm that the disabled extension was skipped while an active core extension was still refreshed:
SELECT
    extension_id,
    name,
    element,
    state,
    manifest_cache = 'DISABLED_SENTINEL' AS disabled_sentinel_unchanged,
    manifest_cache = 'ACTIVE_SENTINEL' AS active_sentinel_unchanged,
    JSON_VALID(manifest_cache) AS valid_manifest_json,
    JSON_UNQUOTE(JSON_EXTRACT(manifest_cache, '$.version')) AS manifest_version
FROM j54_extensions
WHERE type = 'library'
  AND element IN ('joomla', 'phpass')
ORDER BY extension_id;

My result after applying the PR was:

lib_joomla  state=0   active_sentinel_unchanged=0   valid_manifest_json=1   manifest_version=13.1
lib_phpass  state=-1  disabled_sentinel_unchanged=1 valid_manifest_json=0

This confirms that the state=-1 extension was excluded while the normal core extension still had its manifest cache refreshed.

avatar wakqasahmed wakqasahmed - change - 13 Sep 2026
Labels Added: PR-5.4-dev
avatar brianteeman
brianteeman - comment - 13 Sep 2026

Surely the correct approach is to determine why/how those extensions were able to get a state of -1 which should not be possible

avatar richard67
richard67 - comment - 13 Sep 2026

I have now tested this with a real Joomla installation and complete Joomla core updates, rather than relying on code review.

Testing instructions

@wakqasahmed I've asked you to update the testing instructions in the PR description (initial post) so other testers can find them.

avatar richard67
richard67 - comment - 13 Sep 2026

Surely the correct approach is to determine why/how those extensions were able to get a state of -1 which should not be possible

A state of -1 is set for extensions in the discover methods of the diverse installer adapters, e.g. for components here: https://github.com/joomla/joomla-cms/blob/5.4-dev/libraries/src/Installer/Adapter/ComponentAdapter.php#L1279

Later, when installing the discovered extensions, the state is set to zero before trying to store the database record in the extensions table in methods https://github.com/joomla/joomla-cms/blob/5.4-dev/libraries/src/Installer/Adapter/ComponentAdapter.php#L1279 of the installer adapters, e.g. for components here: https://github.com/joomla/joomla-cms/blob/5.4-dev/libraries/src/Installer/Adapter/ComponentAdapter.php#L639

So a state of -1 happens for old core extensions which were not properly uninstalled during a previous core update, or they have been properly uninstalled but later an old backup was restored where the files still were present, so the extension discovery has found them.

Now the question for this PR here is do we want to skip these extension when doing the update, like suggested by this PR?

avatar richard67
richard67 - comment - 13 Sep 2026

Surely the correct approach is to determine why/how those extensions were able to get a state of -1 which should not be possible

A state of -1 is set for extensions in the discover methods of the diverse installer adapters, e.g. for components here: https://github.com/joomla/joomla-cms/blob/5.4-dev/libraries/src/Installer/Adapter/ComponentAdapter.php#L1279

Later, when installing the discovered extensions, the state is set to zero before trying to store the database record in the extensions table in methods prepareDiscoverInstall of the installer adapters, e.g. for components here: https://github.com/joomla/joomla-cms/blob/5.4-dev/libraries/src/Installer/Adapter/ComponentAdapter.php#L639

So a state of -1 happens for old core extensions which were not properly uninstalled during a previous core update, or they have been properly uninstalled but later an old backup was restored where the files still were present, so the extension discovery has found them.

Now the question for this PR here is do we want to skip these extension when doing the update, like suggested by this PR?

avatar wakqasahmed
wakqasahmed - comment - 14 Sep 2026

Thanks for digging into the discover/prepareDiscoverInstall path — that matches what I saw for phpass/plg_quickicon_eos after the migration. My take: yes, skipping them in the refresh query seems like the safer near-term fix regardless of how they got to state=-1 in the first place, since refreshManifestCache() already treats that state as an intentional no-op elsewhere. Happy to adjust the approach if the team decides the real fix belongs in the discovery/migration path instead.

avatar richard67
richard67 - comment - 14 Sep 2026

@wakqasahmed So or so the title "Skip disabled core extensions ..." of your PR is wrong, the extensions are not disabled, they are discovered. The disabled states is in another database column (enabled).

And you still haven't added useful testing instructions for human testers to the description (initial post) of your PR so testers can find them. They will not find them buried in the comment thread of this PR.

I have asked before 2 times to do that.

Add a Comment

Login with GitHub to post a comment