From c6475ff29a0169385055ed635de843218e4d11e8 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 24 May 2026 03:46:05 -0500 Subject: [PATCH] feat: canonical URLs, alias heartbeats, package migration, cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - manifest.xml: package-type plugin → package - Canonical URL injection for alias domains (prevents SEO duplication) - Heartbeat registration for alias domains (each alias gets Grafana datasource) - Package script.php: enable plugins on every install/update, heartbeat on postflight - Remove accidentally committed profile.ps1 and TODO.md - Add profile.ps1 and TODO.md to .gitignore Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 2 + .mokogitea/manifest.xml | 2 +- TODO.md | 12 --- profile.ps1 | 10 --- .../Extension/MokoWaaS.php | 39 +++++++++- src/script.php | 77 +++++++++++++++++-- 6 files changed, 112 insertions(+), 30 deletions(-) delete mode 100644 TODO.md delete mode 100644 profile.ps1 diff --git a/.gitignore b/.gitignore index 4229e703..2f7a4ac2 100644 --- a/.gitignore +++ b/.gitignore @@ -203,3 +203,5 @@ venv/ *.coverage hypothesis/ +profile.ps1 +TODO.md diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index e60e8c03..e4b34d79 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -18,7 +18,7 @@ PHP - plugin + package src/ diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 488a8abc..00000000 --- a/TODO.md +++ /dev/null @@ -1,12 +0,0 @@ -# TODO - -> **Note:** This file is not tracked in version control (.gitignore). It is for local task tracking only. - -## Critical - - - -## Normal - - - -## Low - - diff --git a/profile.ps1 b/profile.ps1 deleted file mode 100644 index 79041c50..00000000 --- a/profile.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -# MokoWaaS PowerShell Profile -# Source this with: . ./profile.ps1 - -$env:MokoWaaS_ROOT = $PSScriptRoot - -function mo { Set-Location $PSScriptRoot } -function mo-src { Set-Location (Join-Path $PSScriptRoot 'src') } - -Write-Host "MokoWaaS profile loaded" -ForegroundColor Cyan -Write-Host " Navigate: mo, mo-src" -ForegroundColor DarkGray diff --git a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php index 20ff63f6..86ec2a21 100644 --- a/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php +++ b/src/packages/plg_system_mokowaas/Extension/MokoWaaS.php @@ -2938,6 +2938,12 @@ class MokoWaaS extends CMSPlugin { $doc->setMetaData('robots', $robots); } + + // Inject canonical URL pointing to the primary domain + $primaryHost = $this->getPrimaryHost(); + $currentUri = Uri::getInstance(); + $canonical = $currentUri->getScheme() . '://' . $primaryHost . $currentUri->toString(['path', 'query']); + $doc->addHeadLink($canonical, 'canonical'); } // ------------------------------------------------------------------ @@ -2970,8 +2976,39 @@ class MokoWaaS extends CMSPlugin $siteUrl = rtrim(Uri::root(), '/'); $siteName = Factory::getConfig()->get('sitename', 'Joomla'); - // Register primary domain only — aliases should not get separate datasources + // Register primary domain $this->sendHeartbeat($siteUrl, $siteName, $healthToken, $app); + + // Register alias domains (subform format) + $aliases = $params->get('site_aliases', ''); + + if (!empty($aliases)) + { + if (is_string($aliases)) + { + $aliases = json_decode($aliases); + } + + if (is_object($aliases)) + { + $aliases = (array) $aliases; + } + + if (is_array($aliases)) + { + foreach ($aliases as $alias) + { + $alias = (object) $alias; + + if (!empty($alias->domain)) + { + $domain = rtrim(trim($alias->domain), '/'); + $aliasUrl = 'https://' . preg_replace('#^https?://#i', '', $domain); + $this->sendHeartbeat($aliasUrl, $siteName, $healthToken, $app); + } + } + } + } } /** diff --git a/src/script.php b/src/script.php index bc35c260..e7ea1512 100644 --- a/src/script.php +++ b/src/script.php @@ -15,7 +15,8 @@ use Joomla\CMS\Log\Log; /** * Package installation script for MokoWaaS. * - * Auto-enables the system plugin and webservices plugin after install. + * Handles migration from standalone plugin to package, enables plugins, + * and triggers heartbeat registration on install/update. * * @since 2.2.0 */ @@ -33,11 +34,11 @@ class Pkg_MokowaasInstallerScript */ public function postflight($type, $parent) { - if ($type === 'install' || $type === 'discover_install') - { - $this->enablePlugin('system', 'mokowaas'); - $this->enablePlugin('webservices', 'mokowaas'); - } + $this->enablePlugin('system', 'mokowaas'); + $this->enablePlugin('webservices', 'mokowaas'); + + // Trigger heartbeat registration + $this->sendHeartbeat(); } /** @@ -69,4 +70,68 @@ class Pkg_MokowaasInstallerScript Log::add('Error enabling plugin ' . $group . '/' . $element . ': ' . $e->getMessage(), Log::WARNING, 'jerror'); } } + + /** + * Send heartbeat to the MokoWaaS monitoring receiver. + * + * @return void + * + * @since 02.03.08 + */ + private function sendHeartbeat(): void + { + try + { + $db = Factory::getDbo(); + $query = $db->getQuery(true) + ->select($db->quoteName('params')) + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas')) + ->where($db->quoteName('type') . ' = ' . $db->quote('plugin')) + ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); + $params = json_decode((string) $db->setQuery($query)->loadResult()); + + $healthToken = $params->health_api_token ?? ''; + + if (empty($healthToken)) + { + return; + } + + $siteUrl = rtrim(\Joomla\CMS\Uri\Uri::root(), '/'); + $siteName = Factory::getConfig()->get('sitename', 'Joomla'); + + $payload = json_encode([ + 'site_url' => $siteUrl, + 'site_name' => $siteName, + 'health_token' => $healthToken, + 'action' => 'register', + ], JSON_UNESCAPED_SLASHES); + + $ch = curl_init('https://bench.mokoconsulting.tech/api/waas-heartbeat/register'); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'X-MokoWaaS-Key: moko-waas-hb-2026-x9k4m', + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $response = curl_exec($ch); + $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($code >= 200 && $code < 300) + { + Factory::getApplication()->enqueueMessage('Grafana heartbeat: site registered', 'message'); + } + } + catch (\Throwable $e) + { + // Silent failure — heartbeat is non-critical + } + } }