Files
MokoSuiteBackup/source/packages/com_mokobackup/src/Field/DatabaseTablesField.php
T
Jonathan Miller a13f7ca6a6
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
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 / Report Issues (push) Has been cancelled
chore: rename src/ to source/ per MokoStandards convention
Update all references in Makefile, manifest.xml, .gitignore, and CI
workflows (ci-joomla, pr-check, repo-health) to use source/ as the
primary directory with src/ as a fallback for compatibility.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:08:33 -05:00

148 lines
5.0 KiB
PHP

<?php
/**
* @package MokoJoomBackup
* @subpackage com_mokobackup
* @author Moko Consulting <hello@mokoconsulting.tech>
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Component\MokoBackup\Administrator\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Language\Text;
class DatabaseTablesField extends FormField
{
protected $type = 'DatabaseTables';
protected function getInput(): string
{
$db = Factory::getDbo();
$tables = $db->getTableList();
$prefix = $db->getPrefix();
// Parse current exclusions (newline-separated, with optional :data-only suffix)
$excludeData = [];
$excludeStructure = [];
if (!empty($this->value)) {
$lines = array_filter(array_map('trim', explode("\n", str_replace("\r", '', $this->value))));
foreach ($lines as $line) {
// Normalize table name to real prefix for comparison
if (str_ends_with($line, ':data-only')) {
$tableName = str_replace('#__', $prefix, substr($line, 0, -10));
$excludeData[$tableName] = true;
} elseif (str_ends_with($line, ':structure-only')) {
$tableName = str_replace('#__', $prefix, substr($line, 0, -15));
$excludeStructure[$tableName] = true;
} else {
// No suffix = exclude both (backward compatible)
$tableName = str_replace('#__', $prefix, $line);
$excludeData[$tableName] = true;
$excludeStructure[$tableName] = true;
}
}
}
$id = htmlspecialchars($this->id, ENT_QUOTES, 'UTF-8');
$name = htmlspecialchars($this->name, ENT_QUOTES, 'UTF-8');
$html = '<div class="mb-2">';
$html .= '<input type="hidden" name="' . $name . '" id="' . $id . '" value="" />';
$html .= '<div class="form-text mb-2">' . Text::_('COM_MOKOBACKUP_FIELD_EXCLUDE_TABLES_HELP') . '</div>';
$html .= '<div class="table-responsive" style="max-height:400px; overflow-y:auto;">';
$html .= '<table class="table table-sm table-hover mb-0">';
$html .= '<thead class="sticky-top bg-white"><tr>';
$html .= '<th class="w-1"><input type="checkbox" id="' . $id . '_toggleData" title="Toggle all data" /></th>';
$html .= '<th class="w-1">' . Text::_('COM_MOKOBACKUP_FIELD_EXCLUDE_DATA') . '</th>';
$html .= '<th class="w-1"><input type="checkbox" id="' . $id . '_toggleStructure" title="Toggle all structure" /></th>';
$html .= '<th class="w-1">' . Text::_('COM_MOKOBACKUP_FIELD_EXCLUDE_STRUCTURE') . '</th>';
$html .= '<th>' . Text::_('COM_MOKOBACKUP_FIELD_TABLE_NAME') . '</th>';
$html .= '</tr></thead><tbody>';
foreach ($tables as $table) {
$dataChecked = isset($excludeData[$table]) ? ' checked' : '';
$structureChecked = isset($excludeStructure[$table]) ? ' checked' : '';
// Convert to #__ notation for storage
$storeValue = $table;
if (str_starts_with($table, $prefix)) {
$storeValue = '#__' . substr($table, \strlen($prefix));
}
$safeValue = htmlspecialchars($storeValue, ENT_QUOTES, 'UTF-8');
$safeTable = htmlspecialchars($table, ENT_QUOTES, 'UTF-8');
$html .= '<tr>';
$html .= '<td></td>';
$html .= '<td><input type="checkbox" class="' . $id . '_data" value="' . $safeValue . '"' . $dataChecked . ' /></td>';
$html .= '<td></td>';
$html .= '<td><input type="checkbox" class="' . $id . '_structure" value="' . $safeValue . '"' . $structureChecked . ' /></td>';
$html .= '<td><code>' . $safeTable . '</code></td>';
$html .= '</tr>';
}
$html .= '</tbody></table></div></div>';
// Script to sync checkboxes to hidden field
$html .= <<<SCRIPT
<script>
(function() {
var hidden = document.getElementById('{$id}');
var dataCbs = document.querySelectorAll('.{$id}_data');
var structCbs = document.querySelectorAll('.{$id}_structure');
var toggleData = document.getElementById('{$id}_toggleData');
var toggleStructure = document.getElementById('{$id}_toggleStructure');
function sync() {
var result = {};
dataCbs.forEach(function(cb) {
if (cb.checked) result[cb.value] = (result[cb.value] || 0) | 1;
});
structCbs.forEach(function(cb) {
if (cb.checked) result[cb.value] = (result[cb.value] || 0) | 2;
});
var lines = [];
for (var table in result) {
if (result[table] === 3) {
lines.push(table);
} else if (result[table] === 1) {
lines.push(table + ':data-only');
} else if (result[table] === 2) {
lines.push(table + ':structure-only');
}
}
hidden.value = lines.join('\\n');
}
dataCbs.forEach(function(cb) { cb.addEventListener('change', sync); });
structCbs.forEach(function(cb) { cb.addEventListener('change', sync); });
toggleData.addEventListener('change', function() {
var state = this.checked;
dataCbs.forEach(function(cb) { cb.checked = state; });
sync();
});
toggleStructure.addEventListener('change', function() {
var state = this.checked;
structCbs.forEach(function(cb) { cb.checked = state; });
sync();
});
sync();
})();
</script>
SCRIPT;
return $html;
}
}