diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 095dac4..14b8ca0 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,7 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: mokocli.Automation -# VERSION: 01.01.03 +# VERSION: 01.02.02 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/CHANGELOG.md b/CHANGELOG.md index f984fc6..ec9fd77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - SQL migration compatibility: removed `DROP COLUMN IF EXISTS` (MySQL 8.0.13+ only) in favor of plain `DROP COLUMN` +- Honest installer success: package postflight now verifies every bundled child extension (component, modules, plugin) actually landed in `#__extensions` before showing the license/next-steps message; if any is missing it shows an error and suppresses the success message instead of reporting a false success. Fails open so a transient DB/IO glitch never fakes a failure. ## [1.1.0] - 2026-06-23 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 3799b3d..eeb9fa6 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -14,7 +14,7 @@ DEFGROUP: INGROUP: Project.Documentation REPO: - VERSION: 01.01.03 + VERSION: 01.02.02 PATH: ./CODE_OF_CONDUCT.md BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default --> diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 253c716..3069030 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -19,7 +19,7 @@ DEFGROUP: mokoconsulting-tech.Template-Joomla INGROUP: MokoStandards.Governance REPO: https://github.com/mokoconsulting-tech/Template-Joomla - VERSION: 01.01.03 + VERSION: 01.02.02 PATH: /GOVERNANCE.md BRIEF: Project governance rules, roles, and decision process for Template-Joomla --> diff --git a/SECURITY.md b/SECURITY.md index f5ff72e..fbf611e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME] INGROUP: [PROJECT_NAME].Documentation REPO: [REPOSITORY_URL] PATH: /SECURITY.md -VERSION: 01.01.03 +VERSION: 01.02.02 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/source/packages/com_mokosuitestorelocator/mokosuitestorelocator.xml b/source/packages/com_mokosuitestorelocator/mokosuitestorelocator.xml index 786cec9..2a5973a 100644 --- a/source/packages/com_mokosuitestorelocator/mokosuitestorelocator.xml +++ b/source/packages/com_mokosuitestorelocator/mokosuitestorelocator.xml @@ -15,7 +15,7 @@ --> com_mokosuitestorelocator - 01.01.03 + 01.02.02 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/mod_mokosuitestorelocator_map/mod_mokosuitestorelocator_map.xml b/source/packages/mod_mokosuitestorelocator_map/mod_mokosuitestorelocator_map.xml index 54935d5..e237f6f 100644 --- a/source/packages/mod_mokosuitestorelocator_map/mod_mokosuitestorelocator_map.xml +++ b/source/packages/mod_mokosuitestorelocator_map/mod_mokosuitestorelocator_map.xml @@ -14,7 +14,7 @@ --> mod_mokosuitestorelocator_map - 01.01.03 + 01.02.02 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/mod_mokosuitestorelocator_search/mod_mokosuitestorelocator_search.xml b/source/packages/mod_mokosuitestorelocator_search/mod_mokosuitestorelocator_search.xml index 8a3e777..72cff31 100644 --- a/source/packages/mod_mokosuitestorelocator_search/mod_mokosuitestorelocator_search.xml +++ b/source/packages/mod_mokosuitestorelocator_search/mod_mokosuitestorelocator_search.xml @@ -14,7 +14,7 @@ --> mod_mokosuitestorelocator_search - 01.01.03 + 01.02.02 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/packages/plg_webservices_mokosuitestorelocator/mokosuitestorelocator.xml b/source/packages/plg_webservices_mokosuitestorelocator/mokosuitestorelocator.xml index 621ac33..6be5b15 100644 --- a/source/packages/plg_webservices_mokosuitestorelocator/mokosuitestorelocator.xml +++ b/source/packages/plg_webservices_mokosuitestorelocator/mokosuitestorelocator.xml @@ -5,7 +5,7 @@ ========================================================================= --> plg_webservices_mokosuitestorelocator - 01.01.03 + 01.02.02 2026-06-24 Moko Consulting hello@mokoconsulting.tech diff --git a/source/pkg_mokosuitestorelocator.xml b/source/pkg_mokosuitestorelocator.xml index 229ac1c..e756649 100644 --- a/source/pkg_mokosuitestorelocator.xml +++ b/source/pkg_mokosuitestorelocator.xml @@ -18,7 +18,7 @@ MokoSuite Store Locator mokosuitestorelocator - 01.01.03 + 01.02.02 2026-06-23 Moko Consulting hello@mokoconsulting.tech diff --git a/source/script.php b/source/script.php index 1f4058e..75616ca 100644 --- a/source/script.php +++ b/source/script.php @@ -128,12 +128,121 @@ class Pkg_MokosuitestorelocatorInstallerScript implements InstallerScriptInterfa */ public function postflight(string $type, InstallerAdapter $parent): bool { + // Housekeeping runs unconditionally. $this->restoreDownloadKey(); + + // Be honest about success. Joomla's package installer only LOGS a failed + // child sub-install but still runs this postflight, so don't show the + // license / next-steps message if a bundled extension is actually missing. + // Fails open (see missingChildExtensions) so a query/IO glitch never fakes + // a failure. + $missing = $this->missingChildExtensions($parent); + + if (!empty($missing)) + { + \Joomla\CMS\Factory::getApplication()->enqueueMessage( + '

MokoSuiteStoreLocator did not install correctly.

' + . '

The following bundled extensions are missing: ' + . htmlspecialchars(implode(', ', $missing), ENT_QUOTES) . '

' + . '

Please uninstall MokoSuiteStoreLocator and reinstall the full package.

', + 'error' + ); + + return true; + } + + // Success / next-steps path — only reached when every child installed. $this->warnMissingLicenseKey(); return true; } + /** + * Verify every child extension declared in the package manifest actually landed + * in #__extensions. Returns readable labels of any that are missing. + * + * Matching per Joomla's #__extensions uniqueness: + * - type = the "type" attribute + * - element = the "id" attribute, EXCEPT for plugins the manifest often + * declares the full `plg__` id, so strip a leading + * `plg__` to get the real element; components/modules use id as-is + * - plugins ALSO match folder = the "group" attribute + * + * FAILS OPEN: any error (unreadable manifest, query failure) returns [] so a + * transient glitch never turns a good install into a false failure. + * + * @param InstallerAdapter $parent The parent installer object. + * + * @return array Readable labels of missing child extensions. + * + * @since 1.0.0 + */ + private function missingChildExtensions(InstallerAdapter $parent): array + { + try + { + $manifest = $parent->getParent()->getManifest(); + + if (!$manifest || !isset($manifest->files) || !isset($manifest->files->file)) + { + return []; + } + + $db = \Joomla\CMS\Factory::getDbo(); + $missing = []; + + foreach ($manifest->files->file as $file) + { + $attrs = $file->attributes(); + $id = isset($attrs['id']) ? (string) $attrs['id'] : ''; + $exType = isset($attrs['type']) ? (string) $attrs['type'] : ''; + + if ($id === '' || $exType === '') + { + continue; + } + + $group = isset($attrs['group']) ? (string) $attrs['group'] : ''; + $element = $id; + + // Plugin element in #__extensions is the id minus any leading plg__. + if ($exType === 'plugin' && $group !== '') + { + $prefix = 'plg_' . $group . '_'; + + if (strpos($element, $prefix) === 0) + { + $element = substr($element, \strlen($prefix)); + } + } + + $query = $db->getQuery(true) + ->select('COUNT(*)') + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' = ' . $db->quote($element)) + ->where($db->quoteName('type') . ' = ' . $db->quote($exType)); + + if ($exType === 'plugin' && $group !== '') + { + $query->where($db->quoteName('folder') . ' = ' . $db->quote($group)); + } + + if ((int) $db->setQuery($query)->loadResult() === 0) + { + $label = trim((string) $file); + $missing[] = $label !== '' ? preg_replace('/\.zip$/i', '', $label) : $id; + } + } + + return $missing; + } + catch (\Throwable $e) + { + // Fail open — never fake a failure on a glitch. + return []; + } + } + private ?string $savedDownloadKey = null; private function saveDownloadKey(): void