feat(demo): multi-select list for tables, formatted next-reset with timezone
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Update Server / Update Server (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Generic: Repo Health / Site Health (push) Has been cancelled
Generic: Repo Health / Access control (push) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Update Server / Update Server (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
- SnapshotTablesField now renders as a multi-select <select> with optgroups (Content, Users, Menus, Modules, Assets, Other) instead of individual checkboxes - NextResetField displays the next reset as a formatted datetime in the site's configured timezone with a relative badge (in X hours) Authored-by: Moko Consulting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage plg_system_mokowaas
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* VERSION: 02.26.00
|
||||
* PATH: /src/Field/NextResetField.php
|
||||
* BRIEF: Read-only field that displays the next scheduled reset in the site timezone
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\System\MokoWaaS\Field;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Form\FormField;
|
||||
|
||||
/**
|
||||
* Displays the next scheduled demo reset as a formatted datetime
|
||||
* in the Joomla site timezone with a relative "in X hours" suffix.
|
||||
*
|
||||
* @since 02.26.00
|
||||
*/
|
||||
class NextResetField extends FormField
|
||||
{
|
||||
protected $type = 'NextReset';
|
||||
|
||||
protected function getInput()
|
||||
{
|
||||
if (empty($this->value))
|
||||
{
|
||||
return '<div class="alert alert-secondary mb-0 py-2">No reset scheduled — save the plugin config to calculate.</div>';
|
||||
}
|
||||
|
||||
$utcTimestamp = strtotime($this->value);
|
||||
|
||||
if ($utcTimestamp === false || $utcTimestamp <= 0)
|
||||
{
|
||||
return '<div class="alert alert-warning mb-0 py-2">Invalid timestamp stored.</div>';
|
||||
}
|
||||
|
||||
// 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 = '<span class="badge bg-warning text-dark">overdue — save to recalculate</span>';
|
||||
}
|
||||
elseif ($diff < 3600)
|
||||
{
|
||||
$mins = (int) ceil($diff / 60);
|
||||
$relative = '<span class="badge bg-info">in ' . $mins . ' minute' . ($mins !== 1 ? 's' : '') . '</span>';
|
||||
}
|
||||
elseif ($diff < 86400)
|
||||
{
|
||||
$hours = round($diff / 3600, 1);
|
||||
$relative = '<span class="badge bg-info">in ' . $hours . ' hour' . ($hours != 1 ? 's' : '') . '</span>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$days = round($diff / 86400, 1);
|
||||
$relative = '<span class="badge bg-secondary">in ' . $days . ' day' . ($days != 1 ? 's' : '') . '</span>';
|
||||
}
|
||||
|
||||
return '<div class="d-flex align-items-center gap-2">'
|
||||
. '<span class="form-control-plaintext" style="font-weight:500">'
|
||||
. '<span class="icon-calendar" aria-hidden="true"></span> '
|
||||
. htmlspecialchars($formatted) . '</span> '
|
||||
. $relative
|
||||
. '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($this->value) . '" />'
|
||||
. '</div>';
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
<field name="demo_next_reset" type="text"
|
||||
<field name="demo_next_reset" type="NextReset"
|
||||
label="PLG_SYSTEM_MOKOWAAS_DEMO_NEXT_RESET_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_DEMO_NEXT_RESET_DESC"
|
||||
readonly="true" default="" />
|
||||
/>
|
||||
<field name="demo_snapshot_tables" type="SnapshotTables"
|
||||
label="PLG_SYSTEM_MOKOWAAS_DEMO_TABLES_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_DEMO_TABLES_DESC"
|
||||
multiple="true"
|
||||
size="15"
|
||||
/>
|
||||
<field name="demo_snapshot_include_media" type="checkboxes"
|
||||
label="PLG_SYSTEM_MOKOWAAS_DEMO_MEDIA_LABEL"
|
||||
|
||||
Reference in New Issue
Block a user