feat: guided tours framework + DevTools reset tour toggle
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Generic: Project CI / Tests (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.2) (pull_request) Blocked by required conditions
Joomla: Extension CI / Tests (PHP 8.3) (pull_request) Blocked by required conditions
Joomla: Extension CI / PHPStan Analysis (pull_request) Blocked by required conditions
Joomla: Extension CI / Build RC Pre-Release (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Joomla: Extension CI / Lint & Validate (pull_request) Failing after 9s
Joomla: Extension CI / Release Readiness Check (pull_request) Failing after 9s
Universal: PR Check / Branch Policy (pull_request) Successful in 3s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 19s
Universal: PR Check / Validate PR (pull_request) Failing after 12s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Joomla: Metadata Validation / Validate Joomla Metadata (pull_request) Successful in 18s
Generic: Project CI / Lint & Validate (pull_request) Successful in 48s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 51s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 54s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 19s
Universal: Auto Version Bump / Version Bump (push) Successful in 14s

- registerGuidedTours() in script.php postflight registers Welcome and
  Firewall tours via Joomla's #__guidedtours/#__guidedtour_steps tables
- Tours use com_mokosuiteclient.* UIDs, auto-update on package update
- DevTools: reset_tour_prompts one-shot toggle clears all guided tour
  completion flags from #__user_profiles on save
- Language keys added for tour reset field
This commit is contained in:
Jonathan Miller
2026-06-23 11:59:46 -05:00
parent bda9ec1192
commit 57b48520af
4 changed files with 222 additions and 65 deletions
+190 -65
View File
@@ -108,6 +108,9 @@ class Pkg_MokosuiteclientInstallerScript
// Set up MokoSuiteClient guided tours and unpublish Joomla defaults
$this->setupGuidedTours();
// Register MokoSuiteClient guided tour content (tours + steps)
$this->registerGuidedTours();
// Clean up orphaned empty-element rows and stale files from old DEFAULT '' bug
$this->cleanupEmptyElements();
@@ -1486,97 +1489,217 @@ class Pkg_MokosuiteclientInstallerScript
);
$db->execute();
// Define MokoSuiteClient tours
// Remove old-format tours (superseded by com_mokosuiteclient.* UIDs)
$oldUids = [
$db->quote('mokosuiteclient-welcome'),
$db->quote('mokosuiteclient-firewall'),
$db->quote('mokosuiteclient-extensions'),
];
// Delete orphaned steps first
$subQuery = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__guidedtours'))
->where($db->quoteName('uid') . ' IN (' . implode(',', $oldUids) . ')');
$db->setQuery($subQuery);
$oldTourIds = $db->loadColumn();
if (!empty($oldTourIds))
{
$db->setQuery(
$db->getQuery(true)
->delete($db->quoteName('#__guidedtour_steps'))
->where($db->quoteName('tour_id') . ' IN (' . implode(',', array_map('intval', $oldTourIds)) . ')')
)->execute();
$db->setQuery(
$db->getQuery(true)
->delete($db->quoteName('#__guidedtours'))
->where($db->quoteName('uid') . ' IN (' . implode(',', $oldUids) . ')')
)->execute();
}
// Tour registration is now handled by registerGuidedTours()
}
catch (\Throwable $e)
{
Log::add('Guided tours setup error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
}
}
/**
* Register MokoSuiteClient guided tours and their steps.
*
* Inserts tour definitions into #__guidedtours and step definitions into
* #__guidedtour_steps. Skips if the tables do not exist (pre-Joomla 4.3)
* or if a tour with the same uid already exists.
*
* @return void
*
* @since 02.47.09
*/
private function registerGuidedTours(): void
{
try
{
$db = Factory::getDbo();
// Check if #__guidedtours table exists (Joomla 4.3+)
$tables = $db->getTableList();
$prefix = $db->getPrefix();
if (!\in_array($prefix . 'guidedtours', $tables, true))
{
return;
}
$now = date('Y-m-d H:i:s');
// Define tours
$tours = [
[
'uid' => 'mokosuiteclient-welcome',
'title' => 'Welcome to MokoSuiteClient',
'desc' => 'Get started with the MokoSuiteClient Admin Tools Suite. This tour shows you the key areas of your admin dashboard.',
'url' => 'administrator/index.php?option=com_mokosuiteclient',
'steps' => [
['title' => 'MokoSuiteClient Dashboard', 'desc' => 'This is your MokoSuiteClient control center. You can see site info, feature plugins, WAF activity, and quick actions all in one place.', 'target' => '#mokosuiteclient-dashboard', 'type' => 0],
['title' => 'Site Information', 'desc' => 'The info bar shows your Joomla version, PHP version, database type, and debug/offline status at a glance.', 'target' => '.mokosuiteclient-info-bar', 'type' => 0],
['title' => 'Quick Actions', 'desc' => 'Use these buttons to clear cache, check updates, manage extensions, and perform common admin tasks with one click.', 'target' => '#mokosuiteclient-btn-cache', 'type' => 0],
['title' => 'Feature Plugins', 'desc' => 'MokoSuiteClient features are split into toggleable plugins. Enable or disable security, tenant restrictions, developer tools, and more from here.', 'target' => '.mokosuiteclient-plugin-grid', 'type' => 0],
['title' => 'MokoSuiteClient Menu', 'desc' => 'The MokoSuiteClient sidebar menu gives you quick access to all admin tools — Helpdesk, Extensions, WAF Log, Database Tools, and more.', 'target' => '.mokosuiteclient-admin-menu, [class*="mokosuiteclient"]', 'type' => 0],
'uid' => 'com_mokosuiteclient.welcome',
'title' => 'MokoSuite Welcome',
'description' => 'Get started with MokoSuite — configure your health token, send your first heartbeat, and set up trusted IPs.',
'extensions' => '["com_mokosuiteclient"]',
'url' => 'administrator/index.php?option=com_mokosuiteclient',
'steps' => [
[
'title' => 'Welcome to MokoSuite',
'description' => 'This is your MokoSuite control panel. Let\'s walk through the key features.',
'target' => '#mokosuiteclient-dashboard',
'type' => 2,
'position' => 'bottom',
],
[
'title' => 'Site Info Bar',
'description' => 'Your site name, version, support PIN, and system info at a glance.',
'target' => '.card.mb-4:first-child',
'type' => 2,
'position' => 'bottom',
],
[
'title' => 'Quick Actions',
'description' => 'Clear cache, check updates, manage extensions, and more.',
'target' => '#mokosuiteclient-btn-cache',
'type' => 2,
'position' => 'right',
],
[
'title' => 'Plugin Cards',
'description' => 'Enable, disable, and configure MokoSuite plugins from here.',
'target' => '.mokosuiteclient-plugin-card:first-child',
'type' => 2,
'position' => 'top',
],
],
],
[
'uid' => 'mokosuiteclient-firewall',
'title' => 'MokoSuiteClient Firewall Setup',
'desc' => 'Configure the Web Application Firewall to protect your site from common attacks.',
'url' => 'administrator/index.php?option=com_plugins&task=plugin.edit&filter[search]=mokosuiteclient_firewall',
'steps' => [
['title' => 'Firewall Plugin', 'desc' => 'The MokoSuiteClient Firewall provides 10 security shields including SQL injection, XSS, and malicious user agent detection.', 'target' => '', 'type' => 0],
['title' => 'WAF Shields', 'desc' => 'Enable or disable individual WAF shields. Each shield protects against a specific attack vector. All shields are enabled by default.', 'target' => '', 'type' => 0],
['title' => 'Security Headers', 'desc' => 'Configure HTTP security headers like X-Frame-Options, Content-Security-Policy, and HSTS to harden your site against browser-based attacks.', 'target' => '', 'type' => 0],
['title' => 'IP Blocklist', 'desc' => 'Block specific IP addresses, CIDR ranges, or wildcard patterns. The auto-ban feature automatically blocks IPs that trigger too many WAF alerts.', 'target' => '', 'type' => 0],
],
],
[
'uid' => 'mokosuiteclient-extensions',
'title' => 'Moko Extensions Manager',
'desc' => 'Browse and install Moko Consulting extensions from the built-in catalog.',
'url' => 'administrator/index.php?option=com_mokosuiteclient&view=extensions',
'steps' => [
['title' => 'Extension Catalog', 'desc' => 'Browse all available Moko Consulting extensions. Each card shows the extension name, description, install status, and current version.', 'target' => '', 'type' => 0],
['title' => 'Install Extensions', 'desc' => 'Click Install to add an extension from the Moko Consulting repository. Updates are handled through Joomla\'s standard update system.', 'target' => '', 'type' => 0],
'uid' => 'com_mokosuiteclient.firewall',
'title' => 'MokoSuite Firewall Setup',
'description' => 'Configure your Web Application Firewall — trusted IPs, WAF shields, and security headers.',
'extensions' => '["com_mokosuiteclient"]',
'url' => 'administrator/index.php?option=com_plugins&task=plugin.edit&extension_id=0',
'steps' => [
[
'title' => 'Your Current IP',
'description' => 'This shows your IP address. Copy it to add to the Trusted IPs list.',
'target' => '#mokosuiteclient-current-ip',
'type' => 2,
'position' => 'bottom',
],
[
'title' => 'Trusted IPs',
'description' => 'Add IPs that should bypass WAF checks — your office, VPN, etc.',
'target' => '#jform_params_trusted_ips',
'type' => 2,
'position' => 'top',
],
[
'title' => 'WAF Shields',
'description' => 'Enable protection against SQL injection, XSS, malicious agents, and more.',
'target' => '#attrib-waf',
'type' => 2,
'position' => 'bottom',
],
],
],
];
foreach ($tours as $tourDef)
{
// Check if tour already exists
$db->setQuery(
$db->getQuery(true)
->select('id')
->from($db->quoteName('#__guidedtours'))
->where($db->quoteName('uid') . ' = ' . $db->quote($tourDef['uid']))
);
// Check if tour already exists by uid
$query = $db->getQuery(true)
->select($db->quoteName('id'))
->from($db->quoteName('#__guidedtours'))
->where($db->quoteName('uid') . ' = ' . $db->quote($tourDef['uid']));
$db->setQuery($query);
$existingId = (int) $db->loadResult();
if ($db->loadResult())
if ($existingId)
{
continue;
// Update existing tour metadata
$update = $db->getQuery(true)
->update($db->quoteName('#__guidedtours'))
->set($db->quoteName('title') . ' = ' . $db->quote($tourDef['title']))
->set($db->quoteName('description') . ' = ' . $db->quote($tourDef['description']))
->set($db->quoteName('extensions') . ' = ' . $db->quote($tourDef['extensions']))
->set($db->quoteName('url') . ' = ' . $db->quote($tourDef['url']))
->set($db->quoteName('published') . ' = 1')
->set($db->quoteName('modified') . ' = ' . $db->quote($now))
->where($db->quoteName('id') . ' = ' . $existingId);
$db->setQuery($update)->execute();
// Delete existing steps so they are re-inserted fresh
$delete = $db->getQuery(true)
->delete($db->quoteName('#__guidedtour_steps'))
->where($db->quoteName('tour_id') . ' = ' . $existingId);
$db->setQuery($delete)->execute();
$tourId = $existingId;
}
else
{
// Insert new tour
$tour = (object) [
'title' => $tourDef['title'],
'uid' => $tourDef['uid'],
'description' => $tourDef['description'],
'extensions' => $tourDef['extensions'],
'url' => $tourDef['url'],
'created' => $now,
'created_by' => 0,
'modified' => $now,
'modified_by' => 0,
'published' => 1,
'language' => '*',
'note' => 'MokoSuiteClient',
'access' => 1,
'ordering' => 0,
'autostart' => 0,
];
$db->insertObject('#__guidedtours', $tour, 'id');
$tourId = (int) $tour->id;
}
$tour = (object) [
'title' => $tourDef['title'],
'uid' => $tourDef['uid'],
'description' => $tourDef['desc'],
'extensions' => '',
'url' => $tourDef['url'],
'created' => date('Y-m-d H:i:s'),
'created_by' => 0,
'modified' => date('Y-m-d H:i:s'),
'modified_by' => 0,
'published' => 1,
'language' => '*',
'note' => 'MokoSuiteClient',
'access' => 3,
'ordering' => 0,
'autostart' => 0,
];
$db->insertObject('#__guidedtours', $tour, 'id');
$tourId = (int) $tour->id;
// Insert steps
foreach ($tourDef['steps'] as $i => $stepDef)
{
$step = (object) [
'tour_id' => $tourId,
'title' => $stepDef['title'],
'description' => $stepDef['desc'],
'description' => $stepDef['description'],
'target' => $stepDef['target'],
'type' => $stepDef['type'],
'interactive_type' => 1,
'url' => '',
'position' => 'bottom',
'position' => $stepDef['position'],
'ordering' => $i + 1,
'published' => 1,
'created' => date('Y-m-d H:i:s'),
'created' => $now,
'created_by' => 0,
'modified' => date('Y-m-d H:i:s'),
'modified' => $now,
'modified_by' => 0,
'language' => '*',
'note' => '',
@@ -1586,10 +1709,12 @@ class Pkg_MokosuiteclientInstallerScript
$db->insertObject('#__guidedtour_steps', $step, 'id');
}
}
Log::add('Registered ' . \count($tours) . ' MokoSuiteClient guided tours.', Log::INFO, 'mokosuiteclient');
}
catch (\Throwable $e)
{
Log::add('Guided tours setup error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
Log::add('Guided tour registration error: ' . $e->getMessage(), Log::WARNING, 'mokosuiteclient');
}
}