---
.../Field/NextResetField.php | 93 +++++++++++++++++++
.../Field/SnapshotTablesField.php | 93 +++++++++----------
src/packages/plg_system_mokowaas/mokowaas.xml | 6 +-
3 files changed, 143 insertions(+), 49 deletions(-)
create mode 100644 src/packages/plg_system_mokowaas/Field/NextResetField.php
diff --git a/src/packages/plg_system_mokowaas/Field/NextResetField.php b/src/packages/plg_system_mokowaas/Field/NextResetField.php
new file mode 100644
index 0000000..5af76e1
--- /dev/null
+++ b/src/packages/plg_system_mokowaas/Field/NextResetField.php
@@ -0,0 +1,93 @@
+value))
+ {
+ return 'No reset scheduled — save the plugin config to calculate.
';
+ }
+
+ $utcTimestamp = strtotime($this->value);
+
+ if ($utcTimestamp === false || $utcTimestamp <= 0)
+ {
+ return 'Invalid timestamp stored.
';
+ }
+
+ // Convert to site timezone
+ $siteTimezone = Factory::getApplication()->get('offset', 'UTC');
+
+ try
+ {
+ $dt = new \DateTime('@' . $utcTimestamp);
+ $dt->setTimezone(new \DateTimeZone($siteTimezone));
+ $formatted = $dt->format('l, F j, Y \a\t g:i A T');
+ }
+ catch (\Throwable $e)
+ {
+ $formatted = gmdate('Y-m-d H:i:s', $utcTimestamp) . ' UTC';
+ }
+
+ // Calculate relative time
+ $diff = $utcTimestamp - time();
+ $relative = '';
+
+ if ($diff <= 0)
+ {
+ $relative = 'overdue — save to recalculate';
+ }
+ elseif ($diff < 3600)
+ {
+ $mins = (int) ceil($diff / 60);
+ $relative = 'in ' . $mins . ' minute' . ($mins !== 1 ? 's' : '') . '';
+ }
+ elseif ($diff < 86400)
+ {
+ $hours = round($diff / 3600, 1);
+ $relative = 'in ' . $hours . ' hour' . ($hours != 1 ? 's' : '') . '';
+ }
+ else
+ {
+ $days = round($diff / 86400, 1);
+ $relative = 'in ' . $days . ' day' . ($days != 1 ? 's' : '') . '';
+ }
+
+ return ''
+ . ''
+ . ' '
+ . htmlspecialchars($formatted) . ' '
+ . $relative
+ . ''
+ . '
';
+ }
+}
diff --git a/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php b/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
index 7a4e891..c61efa6 100644
--- a/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
+++ b/src/packages/plg_system_mokowaas/Field/SnapshotTablesField.php
@@ -8,9 +8,9 @@
* FILE INFORMATION
* DEFGROUP: Joomla.Plugin
* INGROUP: MokoWaaS
- * VERSION: 02.25.03
+ * VERSION: 02.26.00
* PATH: /src/Field/SnapshotTablesField.php
- * BRIEF: Multi-select field that loads DB tables with sensible defaults pre-checked
+ * BRIEF: Multi-select list field that loads DB tables with sensible defaults
*/
namespace Moko\Plugin\System\MokoWaaS\Field;
@@ -18,16 +18,16 @@ namespace Moko\Plugin\System\MokoWaaS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
-use Joomla\CMS\Form\Field\CheckboxesField;
+use Joomla\CMS\Form\Field\ListField;
+use Joomla\CMS\HTML\HTMLHelper;
/**
- * Renders a checkbox list of all Joomla database tables, with content-related
- * tables pre-selected by default. Tables are grouped by category (content,
- * users, menus, modules, other).
+ * Renders a multi-select list box of all Joomla database tables, with
+ * content-related tables pre-selected by default.
*
- * @since 02.25.00
+ * @since 02.26.00
*/
-class SnapshotTablesField extends CheckboxesField
+class SnapshotTablesField extends ListField
{
protected $type = 'SnapshotTables';
@@ -56,17 +56,17 @@ class SnapshotTablesField extends CheckboxesField
];
/**
- * Group labels for table categorisation.
+ * Table suffixes grouped by category for optgroup display.
*
* @var array
* @since 02.25.00
*/
private const TABLE_GROUPS = [
- 'content' => ['content', 'categories', 'fields', 'tags', 'contentitem_tag_map', 'ucm_content', 'ucm_history'],
- 'users' => ['users', 'user_usergroup_map', 'user_profiles', 'usergroups', 'user_keys', 'user_mfa'],
- 'menus' => ['menu', 'menu_types'],
- 'modules' => ['modules', 'modules_menu'],
- 'assets' => ['assets'],
+ 'Content' => ['content', 'categories', 'fields', 'fields_values', 'fields_groups', 'tags', 'contentitem_tag_map', 'ucm_content', 'ucm_history'],
+ 'Users' => ['users', 'user_usergroup_map', 'user_profiles', 'usergroups', 'user_keys', 'user_mfa'],
+ 'Menus' => ['menu', 'menu_types'],
+ 'Modules' => ['modules', 'modules_menu'],
+ 'Assets' => ['assets'],
];
protected function getOptions()
@@ -75,68 +75,65 @@ class SnapshotTablesField extends CheckboxesField
$prefix = $db->getPrefix();
$tables = $db->getTableList();
- $options = [];
+ $grouped = [];
foreach ($tables as $table)
{
- // Only show tables with the site's prefix
if (strpos($table, $prefix) !== 0)
{
continue;
}
- // Convert real table name to #__ notation
- $logical = '#__' . substr($table, strlen($prefix));
+ $suffix = substr($table, strlen($prefix));
+ $logical = '#__' . $suffix;
- // Determine group for display ordering
$group = 'Other';
foreach (self::TABLE_GROUPS as $groupName => $patterns)
{
- $suffix = substr($table, strlen($prefix));
-
- foreach ($patterns as $pattern)
+ if (in_array($suffix, $patterns, true))
{
- if ($suffix === $pattern)
- {
- $group = ucfirst($groupName);
- break 2;
- }
+ $group = $groupName;
+ break;
}
}
- $obj = (object) [
- 'value' => $logical,
- 'text' => $logical,
- 'disable' => false,
- 'class' => '',
- 'onclick' => '',
- ];
-
- $options[$group][] = $obj;
+ $grouped[$group][] = HTMLHelper::_('select.option', $logical, $logical);
}
- // Flatten with group headers: content tables first, then alphabetical
+ // Build options with optgroups: priority groups first
+ $options = [];
$priority = ['Content', 'Users', 'Menus', 'Modules', 'Assets'];
- $sorted = [];
foreach ($priority as $g)
{
- if (isset($options[$g]))
+ if (!empty($grouped[$g]))
{
- $sorted = array_merge($sorted, $options[$g]);
- unset($options[$g]);
+ $options[] = HTMLHelper::_('select.optgroup', '— ' . $g . ' —');
+
+ foreach ($grouped[$g] as $opt)
+ {
+ $options[] = $opt;
+ }
+
+ $options[] = HTMLHelper::_('select.optgroup', '— ' . $g . ' —');
+ unset($grouped[$g]);
}
}
- // Remaining tables (Other)
- if (isset($options['Other']))
+ if (!empty($grouped['Other']))
{
- sort($options['Other']);
- $sorted = array_merge($sorted, $options['Other']);
+ $options[] = HTMLHelper::_('select.optgroup', '— Other —');
+
+ foreach ($grouped['Other'] as $opt)
+ {
+ $options[] = $opt;
+ }
+
+ $options[] = HTMLHelper::_('select.optgroup', '— Other —');
}
- return $sorted;
+ return $options;
}
protected function getInput()
@@ -148,10 +145,12 @@ class SnapshotTablesField extends CheckboxesField
}
elseif (is_string($this->value))
{
- // Handle legacy textarea format (newline-separated)
$this->value = array_filter(array_map('trim', explode("\n", $this->value)));
}
+ // Force multiple attribute
+ $this->__set('multiple', true);
+
return parent::getInput();
}
}
diff --git a/src/packages/plg_system_mokowaas/mokowaas.xml b/src/packages/plg_system_mokowaas/mokowaas.xml
index 2dcecb8..5bf5e75 100644
--- a/src/packages/plg_system_mokowaas/mokowaas.xml
+++ b/src/packages/plg_system_mokowaas/mokowaas.xml
@@ -314,13 +314,15 @@
description="PLG_SYSTEM_MOKOWAAS_DEMO_CRON_DESC"
default="" hint="min hour day month weekday (e.g. 0 */6 * * *)"
showon="demo_reset_schedule:custom" />
-
+ />
Date: Sun, 31 May 2026 00:38:51 +0000
Subject: [PATCH 04/12] chore(version): auto-bump 02.26.01-dev [skip ci]
---
.mokogitea/manifest.xml | 2 +-
.mokogitea/workflows/issue-branch.yml | 2 +-
CHANGELOG.md | 2 +-
CODE_OF_CONDUCT.md | 2 +-
GOVERNANCE.md | 2 +-
LICENSE.md | 2 +-
README.md | 2 +-
SECURITY.md | 2 +-
docs/guides/build-guide.md | 4 ++--
docs/guides/configuration-guide.md | 4 ++--
docs/guides/installation-guide.md | 4 ++--
docs/guides/operations-guide.md | 4 ++--
docs/guides/rollback-and-recovery-guide.md | 4 ++--
docs/guides/testing-guide.md | 4 ++--
docs/guides/troubleshooting-guide.md | 4 ++--
docs/guides/upgrade-and-versioning-guide.md | 4 ++--
docs/index.md | 4 ++--
docs/plugin-basic.md | 4 ++--
docs/update-server.md | 2 +-
src/packages/com_mokowaas/mokowaas.xml | 2 +-
src/packages/plg_system_mokowaas/Extension/MokoWaaS.php | 2 +-
src/packages/plg_system_mokowaas/Field/AllowedIpsField.php | 2 +-
src/packages/plg_system_mokowaas/Field/CopyableTokenField.php | 2 +-
src/packages/plg_system_mokowaas/Field/CurrentIpField.php | 2 +-
src/packages/plg_system_mokowaas/Field/NextResetField.php | 2 +-
.../plg_system_mokowaas/Field/SnapshotTablesField.php | 2 +-
.../plg_system_mokowaas/Service/ContentSyncReceiver.php | 2 +-
.../plg_system_mokowaas/Service/ContentSyncService.php | 2 +-
src/packages/plg_system_mokowaas/Service/DemoResetService.php | 2 +-
src/packages/plg_system_mokowaas/mokowaas.xml | 2 +-
src/packages/plg_system_mokowaas/script.php | 2 +-
src/packages/plg_system_mokowaas/services/provider.php | 2 +-
src/packages/plg_task_mokowaasdemo/mokowaasdemo.xml | 2 +-
src/packages/plg_webservices_mokowaas/mokowaas.xml | 2 +-
.../plg_webservices_perfectpublisher/perfectpublisher.xml | 2 +-
.../plg_webservices_perfectpublisher/services/provider.php | 2 +-
.../src/Extension/PerfectPublisherApi.php | 2 +-
src/pkg_mokowaas.xml | 2 +-
updates.xml | 2 +-
39 files changed, 49 insertions(+), 49 deletions(-)
diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml
index 1d74cef..884e8be 100644
--- a/.mokogitea/manifest.xml
+++ b/.mokogitea/manifest.xml
@@ -9,7 +9,7 @@
Package - MokoWaaS
MokoConsulting
White-label identity, security hardening, and tenant restriction layer for WaaS-managed Joomla environments
- 02.25.03
+ 02.26.01
GNU General Public License v3