refactor: restructure template into samples/ + source/ layout

Replace per-type types/<type>/ scaffolds with a samples/ reference
directory (manifest XML + install script templates for all six
extension types) and a single source/ build directory that CI scans.

- Add samples/manifest/*.xml for component, template, module, plugin,
  package, and library
- Add samples/script/*.php install/update script templates
- Add source/ as the canonical build root (CI scans source/src/htdocs)
- Remove types/ per-type scaffolds
- Rewrite root README for the new structure and expanded CI suite
- Anchor Python MANIFEST ignore rule to root (/MANIFEST) so it no
  longer swallows samples/manifest/ on case-insensitive filesystems
- Expand ci-joomla.yml: SQL, language-key, PHPCS, security, updates.xml,
  asset, MVC naming, router, ACL, webservices, ZIP dry-run, JS/CSS checks

Authored-by: Moko Consulting
This commit is contained in:
2026-07-03 19:51:10 -05:00
parent 08a3e7b089
commit d46c431189
32 changed files with 1958 additions and 1252 deletions
+176
View File
@@ -0,0 +1,176 @@
<?php
/**
* ============================================================================
* INSTRUCTIONS FOR USE:
* 1. This file template serves as the standard structure for all script files
* across the extension repositories. Copy and implement this pattern
* universally in every script file to maintain consistency.
* 2. Replace '{REPONAME}' with the actual name of the repo (UPPERCASE).
* Example: "MOKOSUITEBACKUP"
* 3. Replace '{PACKAGENAME}' with the Joomla extension element name (lowercase).
* Example: "pkg_mokosuitebackup", "com_mokosuitebackup", "mod_mokosuitebackup"
* ============================================================================
*/
/**
* @package Joomla.Installer
* @subpackage {PACKAGENAME}
*/
use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
defined('_JEXEC') || die;
class {REPONAME}InstallerScript
{
/**
* @var string The saved download key cached during preflight to survive updates
*/
private $savedDownloadKey = '';
/**
* Preflight hook runs before any installation/update/uninstallation action.
*/
public function preflight($type, $parent): bool
{
if ($type === 'update') {
$this->backupDownloadKey();
}
return true;
}
/**
* Postflight hook runs after any installation/update/uninstallation action.
*/
public function postflight($type, $parent): void
{
if ($type === 'install') {
$this->installSuccessful();
$this->warnMissingLicenseKey();
}
if ($type === 'update') {
$this->restoreDownloadKey();
$this->installSuccessful();
}
}
public function install($parent): void {}
public function update($parent): void {}
public function uninstall($parent): void {}
/**
* Cache the existing download key from the update sites table before update runs.
*/
private function backupDownloadKey(): void
{
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('us.extra_query'))
->from($db->quoteName('#__update_sites', 'us'))
->join(
'INNER',
$db->quoteName('#__update_sites_extensions', 'use')
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
)
->join(
'INNER',
$db->quoteName('#__extensions', 'e')
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
)
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
->where($db->quoteName('e.type') . ' = ' . $db->quote('component'))
->setLimit(1);
$db->setQuery($query);
$extraQuery = (string) $db->loadResult();
if (!empty($extraQuery)) {
parse_str($extraQuery, $output);
$this->savedDownloadKey = $output['dlid'] ?? $extraQuery;
}
} catch (\Exception $e) {
Log::add('{REPONAME}: Could not backup download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
}
/**
* Restore the download key to the (possibly new) update site record.
*/
private function restoreDownloadKey(): void
{
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName('us.update_site_id'))
->from($db->quoteName('#__update_sites', 'us'))
->join(
'INNER',
$db->quoteName('#__update_sites_extensions', 'use')
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
)
->join(
'INNER',
$db->quoteName('#__extensions', 'e')
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
)
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
->where($db->quoteName('e.type') . ' = ' . $db->quote('component'))
->setLimit(1);
$db->setQuery($query);
$updateSiteId = (int) $db->loadResult();
if ($updateSiteId > 0 && !empty($this->savedDownloadKey)) {
$query = $db->getQuery(true)
->update($db->quoteName('#__update_sites'))
->set($db->quoteName('extra_query') . ' = ' . $db->quote('dlid=' . $this->savedDownloadKey))
->where($db->quoteName('update_site_id') . ' = ' . $updateSiteId);
$db->setQuery($query);
$db->execute();
}
} catch (\Exception $e) {
Log::add('{REPONAME}: Could not restore download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
Factory::getApplication()->enqueueMessage(
'<h4>{REPONAME}</h4>'
. '<p>Your download/license key could not be preserved during the update.</p>'
. '<p>Please re-enter it in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager to continue receiving updates.</p>',
'warning'
);
}
}
/**
* Show post-install license key prompt
*/
private function warnMissingLicenseKey(): void
{
try {
Factory::getApplication()->enqueueMessage(
'<h4>{REPONAME} License Key Required</h4>'
. '<p>A download/license key (DLID) is required to receive updates.</p>'
. '<p>Enter your key in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager '
. 'or contact <a class="btn btn-sm btn-warning ms-2" href="https://mokoconsulting.tech/support" target="_blank" rel="noopener">Moko Consulting Support</a> to obtain one.</p>',
'warning'
);
} catch (\Exception $e) {}
}
/**
* Show install successful prompt
*/
private function installSuccessful(): void
{
try {
Factory::getApplication()->enqueueMessage(
'<h4>{REPONAME} installed successfully!</h4>',
'info'
);
} catch (\Exception $e) {}
}
}