feat: site aliases tab with per-alias offline, robots, and backend redirect

Move site aliases from a comma-separated text field in Diagnostics to its
own tab using Joomla's subform repeatable-table layout. Each alias entry
now supports: domain, offline toggle with custom message, robots meta
directive, and backend redirect to primary domain. Frontend stays on the
alias domain while admin requests can be redirected to the primary.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Miller
2026-05-23 18:10:11 -05:00
parent 14e94518ba
commit a92c1ce772
5 changed files with 269 additions and 19 deletions
+167 -8
View File
@@ -96,6 +96,9 @@ class MokoWaaS extends CMSPlugin
// Security: HTTPS redirect (runs for all clients)
$this->enforceHttps();
// Site alias handling: offline page and backend redirect
$this->handleSiteAlias();
// MokoWaaS API endpoints (run before routing)
$mokoAction = $this->app->input->get('mokowaas', '');
@@ -880,14 +883,20 @@ class MokoWaaS extends CMSPlugin
*/
public function onBeforeCompileHead()
{
if (!$this->app->isClient('administrator'))
$doc = $this->app->getDocument();
if ($doc->getType() !== 'html')
{
return;
}
$doc = $this->app->getDocument();
// Inject robots meta tag for alias domains (frontend only)
if ($this->app->isClient('site'))
{
$this->injectAliasRobots($doc);
}
if ($doc->getType() !== 'html')
if (!$this->app->isClient('administrator'))
{
return;
}
@@ -2559,6 +2568,142 @@ class MokoWaaS extends CMSPlugin
}
}
// ------------------------------------------------------------------
// Site Alias handling
// ------------------------------------------------------------------
/**
* Get the alias configuration for the current request domain, if any.
*
* @return object|null Alias entry object or null if not an alias domain
*
* @since 02.01.43
*/
protected function getCurrentAlias()
{
$currentHost = $_SERVER['HTTP_HOST'] ?? '';
$primaryHost = parse_url(Uri::root(), PHP_URL_HOST);
if (empty($currentHost) || strcasecmp($currentHost, $primaryHost) === 0)
{
return null;
}
$aliases = $this->params->get('site_aliases', '');
if (empty($aliases))
{
return null;
}
// Subform returns JSON string or array
if (is_string($aliases))
{
$aliases = json_decode($aliases, false);
}
if (!is_array($aliases))
{
return null;
}
foreach ($aliases as $alias)
{
$alias = (object) $alias;
if (isset($alias->domain) && strcasecmp(trim($alias->domain), $currentHost) === 0)
{
return $alias;
}
}
return null;
}
/**
* Handle site alias logic: offline page and backend redirect.
*
* Runs early in onAfterInitialise before routing occurs.
*
* @return void
*
* @since 02.01.43
*/
protected function handleSiteAlias()
{
$alias = $this->getCurrentAlias();
if ($alias === null)
{
return;
}
// Backend redirect: send admin requests to the primary domain
if (!empty($alias->redirect_backend) && $alias->redirect_backend === '1'
&& $this->app->isClient('administrator'))
{
$primaryUrl = rtrim(Uri::root(), '/') . '/administrator' . Uri::getInstance()->toString(['path', 'query']);
$adminPath = str_replace(Uri::root() . 'administrator', '', Uri::getInstance()->toString(['path', 'query']));
$primaryUrl = rtrim(Uri::root(), '/') . '/administrator' . $adminPath;
$this->app->redirect($primaryUrl, 301);
}
// Offline: show maintenance page for frontend requests
if (!empty($alias->offline) && $alias->offline === '1'
&& $this->app->isClient('site'))
{
// Allow health API to still respond
if ($this->app->input->get('mokowaas', '') !== '')
{
return;
}
$message = $alias->offline_message ?? 'This site is currently offline for maintenance.';
$brandName = $this->params->get('brand_name', 'MokoWaaS');
header('HTTP/1.1 503 Service Unavailable');
header('Retry-After: 3600');
header('Content-Type: text/html; charset=utf-8');
echo '<!DOCTYPE html><html><head><meta charset="utf-8">';
echo '<meta name="robots" content="noindex, nofollow">';
echo '<title>' . htmlspecialchars($brandName) . ' - Maintenance</title>';
echo '<style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f5f5f5;color:#333}';
echo '.container{text-align:center;padding:2rem;max-width:600px}h1{color:#1a2744;margin-bottom:1rem}p{font-size:1.1rem;line-height:1.6}</style>';
echo '</head><body><div class="container">';
echo '<h1>' . htmlspecialchars($brandName) . '</h1>';
echo '<p>' . htmlspecialchars($message) . '</p>';
echo '</div></body></html>';
$this->app->close();
}
}
/**
* Inject robots meta tag for alias domains.
*
* @param \Joomla\CMS\Document\HtmlDocument $doc Document object
*
* @return void
*
* @since 02.01.43
*/
protected function injectAliasRobots($doc)
{
$alias = $this->getCurrentAlias();
if ($alias === null)
{
return;
}
$robots = $alias->robots ?? 'index, follow';
if ($robots !== 'index, follow')
{
$doc->setMetaData('robots', $robots);
}
}
// ------------------------------------------------------------------
// Heartbeat (called from onExtensionAfterSave)
// ------------------------------------------------------------------
@@ -2591,16 +2736,30 @@ class MokoWaaS extends CMSPlugin
// Register primary domain
$this->sendHeartbeat($siteUrl, $siteName, $healthToken, $app);
// Register any alias domains
// Register any alias domains (subform format: array of objects with domain key)
$aliases = $params->get('site_aliases', '');
if (!empty($aliases))
{
foreach (array_filter(array_map('trim', explode(',', $aliases))) as $alias)
if (is_string($aliases))
{
$aliasUrl = 'https://' . ltrim($alias, 'https://');
$aliasUrl = rtrim($aliasUrl, '/');
$this->sendHeartbeat($aliasUrl, $siteName . ' (' . $alias . ')', $healthToken, $app);
$aliases = json_decode($aliases, false);
}
if (is_array($aliases))
{
foreach ($aliases as $alias)
{
$alias = (object) $alias;
if (!empty($alias->domain))
{
$domain = trim($alias->domain);
$aliasUrl = 'https://' . preg_replace('#^https?://#i', '', $domain);
$aliasUrl = rtrim($aliasUrl, '/');
$this->sendHeartbeat($aliasUrl, $siteName, $healthToken, $app);
}
}
}
}
}
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<form>
<field
name="domain"
type="text"
label="PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_DESC"
required="true"
hint="e.g. www.example.com"
/>
<field
name="offline"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_DESC"
default="0"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field
name="offline_message"
type="textarea"
label="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_DESC"
default=""
rows="3"
showon="offline:1"
/>
<field
name="robots"
type="list"
label="PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_DESC"
default="index, follow"
>
<option value="index, follow">index, follow</option>
<option value="noindex, follow">noindex, follow</option>
<option value="index, nofollow">index, nofollow</option>
<option value="noindex, nofollow">noindex, nofollow</option>
<option value="none">none</option>
</field>
<field
name="redirect_backend"
type="radio"
label="PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_LABEL"
description="PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_DESC"
default="1"
class="btn-group btn-group-yesno"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
</form>
+15 -2
View File
@@ -130,5 +130,18 @@ PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_DESC="Comma-separated list of allowed file exte
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_LABEL="Max Upload Size (MB)"
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_DESC="Maximum file upload size in megabytes."
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Site Aliases"
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Comma-separated list of additional domains this site is accessible on (e.g. www.example.com,alias.example.com). Each alias gets its own Grafana datasource for health monitoring."
; ===== Site Aliases fieldset =====
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_LABEL="Site Aliases"
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior."
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Domain Aliases"
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own Grafana monitoring datasource."
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_LABEL="Domain"
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_DESC="The alias domain name (e.g. www.example.com). Do not include https:// prefix."
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_LABEL="Offline"
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_DESC="Show an offline maintenance page when visitors access the site through this alias domain."
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_LABEL="Offline Message"
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_DESC="Custom message to display when this alias is set to offline."
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_LABEL="Robots"
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_DESC="Meta robots directive for this alias domain. Use 'noindex, nofollow' to prevent search engines from indexing the alias."
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_LABEL="Redirect Backend"
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_DESC="Redirect admin panel requests on this alias to the primary domain. Frontend stays on the alias domain."
+15 -2
View File
@@ -130,5 +130,18 @@ PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_DESC="Comma-separated list of allowed file exte
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_LABEL="Max Upload Size (MB)"
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_DESC="Maximum file upload size in megabytes."
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Site Aliases"
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Comma-separated list of additional domains this site is accessible on (e.g. www.example.com,alias.example.com). Each alias gets its own Grafana datasource for health monitoring."
; ===== Site Aliases fieldset =====
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_LABEL="Site Aliases"
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior."
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Domain Aliases"
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own Grafana monitoring datasource."
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_LABEL="Domain"
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_DESC="The alias domain name (e.g. www.example.com). Do not include https:// prefix."
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_LABEL="Offline"
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_DESC="Show an offline maintenance page when visitors access the site through this alias domain."
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_LABEL="Offline Message"
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_DESC="Custom message to display when this alias is set to offline."
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_LABEL="Robots"
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_DESC="Meta robots directive for this alias domain. Use 'noindex, nofollow' to prevent search engines from indexing the alias."
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_LABEL="Redirect Backend"
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_DESC="Redirect admin panel requests on this alias to the primary domain. Frontend stays on the alias domain."
+17 -7
View File
@@ -44,6 +44,7 @@
<filename plugin="mokowaas">script.php</filename>
<folder>Extension</folder>
<folder>Field</folder>
<folder>forms</folder>
<folder>payload</folder>
<folder>services</folder>
<folder>language</folder>
@@ -268,6 +269,22 @@
description="PLG_SYSTEM_MOKOWAAS_HIDDEN_MENUS_DESC"
rows="5" filter="raw" />
</fieldset>
<fieldset name="site_aliases"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_DESC"
>
<field
name="site_aliases"
type="subform"
label="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL"
description="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC"
formsource="plugins/system/mokowaas/forms/alias_entry.xml"
multiple="true"
layout="joomla.form.field.subform.repeatable-table"
groupByFieldset="false"
buttons="add,remove,move"
/>
</fieldset>
<fieldset name="diagnostics"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_LABEL"
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_DESC"
@@ -281,13 +298,6 @@
filter="raw"
readonly="true"
/>
<field
name="site_aliases"
type="text"
label="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL"
description="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC"
default=""
/>
</fieldset>
<fieldset name="security"
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_LABEL"