admin_url('admin-post.php?action=abcode_dbe_download'),
'downloadNonce' => wp_create_nonce('abcode_dbe_download_nonce'),
'ajaxUrl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('abcode_dbe_nonce'),
'i18n' => [
'standardSql' => __('Standard SQL file', 'abcode-database-exporter'),
'compressedSmaller' => __('Compressed, smaller file', 'abcode-database-exporter'),
'justNow' => __('just now', 'abcode-database-exporter'),
/* translators: %d: number of minutes */
'minuteAgo' => __('%d minute ago', 'abcode-database-exporter'),
/* translators: %d: number of minutes */
'minutesAgo' => __('%d minutes ago', 'abcode-database-exporter'),
/* translators: %d: number of hours */
'hourAgo' => __('%d hour ago', 'abcode-database-exporter'),
/* translators: %d: number of hours */
'hoursAgo' => __('%d hours ago', 'abcode-database-exporter'),
/* translators: %d: number of days */
'dayAgo' => __('%d day ago', 'abcode-database-exporter'),
/* translators: %d: number of days */
'daysAgo' => __('%d days ago', 'abcode-database-exporter'),
/* translators: %s: error message */
'error' => __('Error: %s', 'abcode-database-exporter'),
'retryExport' => __('Retry Export', 'abcode-database-exporter'),
'selectTables' => __('Select tables to export', 'abcode-database-exporter'),
/* translators: %s: table count label (e.g. "5 tables") */
'exportBtn' => __('Export (%s)', 'abcode-database-exporter'),
'restartExport' => __('Restart Export', 'abcode-database-exporter'),
/* translators: %s: file format (e.g. .sql, .sql.gz) */
'downloadFormat' => __('Download .%s', 'abcode-database-exporter'),
/* translators: %d: number of tables */
'tablesSingular' => __('%d table', 'abcode-database-exporter'),
/* translators: %d: number of tables */
'tablesPlural' => __('%d tables', 'abcode-database-exporter'),
'exporting' => __('Exporting...', 'abcode-database-exporter'),
'startingExport' => __('Starting export...', 'abcode-database-exporter'),
'finalisingExport' => __('Finalising export...', 'abcode-database-exporter'),
'done' => __('Done!', 'abcode-database-exporter'),
/* translators: %s: relative time (e.g. "5 minutes ago") */
'lastExported' => __('Last exported: %s', 'abcode-database-exporter'),
'exportAgain' => __('Export Again', 'abcode-database-exporter'),
'networkErrorFinalise' => __('Network error during finalisation', 'abcode-database-exporter'),
/* translators: %1$d: current table number, %2$d: total tables, %3$s: table name */
'exportingTable' => __('Exporting table %1$d of %2$d: %3$s', 'abcode-database-exporter'),
/* translators: %s: table name */
'failedExportTable' => __('Failed to export table: %s', 'abcode-database-exporter'),
/* translators: %s: table name */
'networkErrorTable' => __('Network error exporting table: %s', 'abcode-database-exporter'),
'networkErrorStart' => __('Network error starting export', 'abcode-database-exporter'),
'failedStartExport' => __('Failed to start export', 'abcode-database-exporter'),
'failed' => __('Failed', 'abcode-database-exporter'),
'schemaCopied' => __('CREATE TABLE copied to clipboard', 'abcode-database-exporter'),
'copySchema' => __('Copy CREATE TABLE to clipboard', 'abcode-database-exporter'),
'downloadCsv' => __('Download as CSV', 'abcode-database-exporter'),
'exportSql' => __('Export as SQL', 'abcode-database-exporter'),
],
]
);
wp_add_inline_script('abcode-dbe', abcode_dbe_inline_js());
});
/**
* Returns the plugin's admin CSS as a string for wp_add_inline_style().
*
* @return string CSS content.
*/
function abcode_dbe_inline_css() {
ob_start();
?>
.abcode-dbe-banner {
background: #0f0d25;
margin: -1px -20px 0;
display: flex;
align-items: center;
gap: 18px;
height: 60px;
overflow: hidden;
padding: 15px 32px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 14px;
color: #fff;
position: relative;
border-bottom: 3px solid rgba(255, 255, 255, 0.1);
}
.abcode-dbe-banner-badge {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
z-index: 10;
background: linear-gradient(0deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0.05) 100%);
color: rgb(253, 251, 255);
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
padding: 5px 20px;
border-radius: 0 0 8px 8px;
border-top: none;
border-right: 0.5px solid rgba(255,255,255,0.12);
border-bottom: 0.5px solid rgba(255,255,255,0.12);
border-left: 0.5px solid rgba(255,255,255,0.12);
line-height: 1;
height: 15px;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: rgba(0,0,0,0.18) 0 0 2px;
}
.abcode-dbe-banner-logo {
width: 46px;
height: 46px;
flex-shrink: 0;
}
.abcode-dbe-banner-logo svg {
width: 46px;
height: 46px;
}
.abcode-dbe-banner-title-wrap {
display: flex;
flex-direction: column;
gap: 2px;
}
.abcode-dbe-banner-title-row {
display: flex;
align-items: center;
gap: 10px;
}
.abcode-dbe-banner-title-row h1 {
margin: 0;
font-size: 23px;
font-weight: 600;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: normal;
position: relative;
}
.abcode-dbe-banner-separator {
color: rgba(255,255,255,0.3);
font-size: 18px;
font-weight: 300;
}
.abcode-dbe-banner-section {
background: linear-gradient(135deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0.05) 100%);
color: rgba(255,255,255,0.7);
opacity: 0.9;
font-size: 12px;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 1px;
line-height: normal;
padding: 6px 12px;
border-radius: 20px;
border: 1px solid rgba(255,255,255,0.1);
align-self: center;
position: relative;
top: 1px;
}
.abcode-dbe-banner-subtitle {
font-size: 11px;
color: rgba(255,255,255,0.4);
}
.abcode-dbe-banner-subtitle a {
color: rgba(255,255,255,0.4);
text-decoration: none;
}
.abcode-dbe-banner-subtitle a:hover {
color: rgba(255,255,255,0.7);
}
.abcode-dbe-rate {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
margin-top: 16px;
color: #646970;
text-decoration: none;
font-size: 13px;
transition: color 0.15s;
}
.abcode-dbe-rate:hover {
color: #0f0d25;
}
.abcode-dbe-rate svg {
fill: #f5a623;
}
.abcode-dbe-wrap {
max-width: 680px;
margin: 30px auto 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.abcode-dbe-card {
background: #fff;
border: none;
border-radius: 10px;
box-shadow: 0 1px 4px rgba(15,13,37,0.08), 0 4px 16px rgba(15,13,37,0.04);
padding: 32px;
}
.abcode-dbe-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
margin-bottom: 28px;
}
.abcode-dbe-stat {
background: #f4f0fa;
border: 1px solid #e2dff0;
border-radius: 8px;
padding: 16px;
text-align: center;
}
.abcode-dbe-stat .label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: #646970;
margin-bottom: 4px;
}
.abcode-dbe-stat .value {
font-size: 22px;
font-weight: 600;
color: #0f0d25;
}
.abcode-dbe-stat .value.loading {
color: #c3c4c7;
}
.abcode-dbe-table-list {
border: 1px solid #e2dff0;
border-radius: 8px;
margin-bottom: 28px;
overflow: hidden;
}
.abcode-dbe-table-scroll {
max-height: 220px;
overflow-y: auto;
}
.abcode-dbe-table-list.locked .tbl-check,
.abcode-dbe-table-list.locked #abcode-dbe-check-all {
pointer-events: none;
}
.abcode-dbe-table-list.locked .abcode-dbe-table-dl,
.abcode-dbe-table-list.locked .abcode-dbe-table-csv,
.abcode-dbe-table-list.locked .abcode-dbe-table-schema {
pointer-events: none;
opacity: 0.3;
}
.abcode-dbe-table-row {
display: grid;
grid-template-columns: 28px 1fr 60px 170px;
align-items: center;
padding: 12px 16px;
font-size: 13px;
gap: 10px;
border-bottom: 1px solid #f0eef5;
}
.abcode-dbe-table-head {
display: grid;
grid-template-columns: 28px 1fr 60px 170px;
align-items: center;
padding: 10px 16px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: rgba(255,255,255,0.7);
gap: 10px;
background: #0f0d25;
border-bottom: 1px solid rgba(255,255,255,0.08);
position: sticky;
top: 0;
z-index: 1;
}
.abcode-dbe-table-head input {
width: 16px;
height: 16px;
margin: 0;
cursor: pointer;
accent-color: #1eb300;
}
.abcode-dbe-head-rows {
text-align: center;
}
.abcode-dbe-head-action {
text-align: right;
}
.abcode-dbe-table-row:last-child {
border-bottom: none;
}
.abcode-dbe-table-row:nth-child(even) {
background: #faf9fc;
}
.abcode-dbe-table-row .tbl-check {
width: 16px;
height: 16px;
margin: 0;
cursor: pointer;
flex-shrink: 0;
accent-color: #1eb300;
}
.abcode-dbe-table-row .name {
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
font-size: 12px;
color: #0f0d25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.abcode-dbe-table-row .rows {
color: #646970;
font-size: 12px;
white-space: nowrap;
text-align: center;
}
.abcode-dbe-table-row .action-col {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
}
.abcode-dbe-table-row .status-icon {
display: none;
width: 16px;
height: 16px;
flex-shrink: 0;
}
.abcode-dbe-table-row.exporting .abcode-dbe-table-dl,
.abcode-dbe-table-row.exported .abcode-dbe-table-dl {
display: none !important;
}
.abcode-dbe-table-row.exporting .action-col::before {
content: '';
display: block;
width: 16px;
height: 16px;
border: 2px solid #e2dff0;
border-top-color: #0f0d25;
border-radius: 50%;
animation: abcode-dbe-spin 0.6s linear infinite;
flex-shrink: 0;
}
.abcode-dbe-table-row.exported .status-icon.check {
display: inline-flex !important;
align-items: center;
justify-content: center;
}
@keyframes abcode-dbe-spin {
to { transform: rotate(360deg); }
}
.abcode-dbe-table-row .status-icon.check svg {
fill: #1eb300;
}
.abcode-dbe-table-dl {
background: none;
border: 1px solid #c3c4c7;
border-radius: 4px;
padding: 3px 8px;
font-size: 11px;
color: #0f0d25;
cursor: pointer;
white-space: nowrap;
transition: all 0.15s;
}
.abcode-dbe-table-dl:hover {
background: #f4f0fa;
border-color: #0f0d25;
}
.abcode-dbe-table-dl:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.abcode-dbe-btn {
display: inline-flex;
align-items: center;
gap: 10px;
background: #0f0d25;
color: #fff;
border: none;
border-radius: 8px;
padding: 14px 28px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
width: 100%;
justify-content: center;
}
.abcode-dbe-btn:hover {
background: #1a1740;
}
.abcode-dbe-btn:disabled {
background: #a7aaad;
cursor: not-allowed;
}
.abcode-dbe-btn svg {
width: 20px;
height: 20px;
fill: currentColor;
}
.abcode-dbe-progress-wrap {
display: none;
margin-top: 16px;
}
.abcode-dbe-progress-wrap.active {
display: block;
}
.abcode-dbe-progress-bar-outer {
width: 100%;
height: 18px;
background: #f4f0fa;
border-radius: 9px;
overflow: hidden;
margin-bottom: 10px;
}
.abcode-dbe-progress-bar-inner {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #1eb300, #5cd645);
border-radius: 9px;
transition: width 0.4s ease;
}
.abcode-dbe-progress-status {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #646970;
margin-bottom: 20px;
}
.abcode-dbe-success {
display: none;
align-items: center;
gap: 10px;
padding: 12px 16px;
background: rgba(30, 179, 0, 0.08);
border: 1px solid rgba(30, 179, 0, 0.3);
border-radius: 8px;
margin-top: 16px;
color: #145a00;
font-size: 14px;
}
.abcode-dbe-success.active {
display: flex;
}
.abcode-dbe-success svg {
width: 22px;
height: 22px;
fill: #1eb300;
flex-shrink: 0;
}
.abcode-dbe-download-area {
display: none;
flex-direction: column;
gap: 16px;
margin-top: 16px;
}
.abcode-dbe-download-area.active {
display: flex;
}
.abcode-dbe-dl-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
background: #1eb300;
color: #fff;
border: none;
padding: 14px 18px;
border-radius: 8px;
font-weight: 600;
font-size: 15px;
cursor: pointer;
transition: background 0.15s;
}
.abcode-dbe-dl-btn:hover {
background: #19a000;
}
.abcode-dbe-dl-btn svg {
width: 18px;
height: 18px;
fill: #fff;
}
.abcode-dbe-search {
display: none;
padding: 10px 16px;
border-bottom: 1px solid #e2dff0;
background: #faf9fc;
}
.abcode-dbe-search.active {
display: block;
}
.abcode-dbe-search input {
width: 100%;
padding: 7px 10px 7px 32px;
border: 1px solid #e2dff0;
border-radius: 6px;
font-size: 13px;
outline: none;
background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23a7aaad'%3E%3Cpath d='M15.5 14h-.79l-.28-.27A6.47 6.47 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'/%3E%3C/svg%3E") 8px center / 16px no-repeat;
box-sizing: border-box;
transition: border-color 0.15s;
}
.abcode-dbe-search input:focus {
border-color: #0f0d25;
box-shadow: 0 0 0 1px rgba(15,13,37,0.2);
}
.abcode-dbe-table-row .abcode-dbe-table-csv,
.abcode-dbe-table-row .abcode-dbe-table-schema {
background: none;
border: 1px solid #c3c4c7;
border-radius: 4px;
padding: 3px 8px;
font-size: 11px;
color: #646970;
cursor: pointer;
white-space: nowrap;
transition: all 0.15s;
}
.abcode-dbe-table-row .abcode-dbe-table-csv:hover,
.abcode-dbe-table-row .abcode-dbe-table-schema:hover {
background: #f4f0fa;
border-color: #0f0d25;
}
.abcode-dbe-table-row .abcode-dbe-table-csv:disabled,
.abcode-dbe-table-row .abcode-dbe-table-schema:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.abcode-dbe-table-row.exporting .abcode-dbe-table-csv,
.abcode-dbe-table-row.exported .abcode-dbe-table-csv,
.abcode-dbe-table-row.exporting .abcode-dbe-table-schema,
.abcode-dbe-table-row.exported .abcode-dbe-table-schema {
display: none !important;
}
.abcode-dbe-last-export {
display: none;
align-items: center;
gap: 8px;
padding: 10px 14px;
background: #f4f0fa;
border: 1px solid #e2dff0;
border-radius: 8px;
margin-bottom: 20px;
font-size: 13px;
color: #0f0d25;
}
.abcode-dbe-last-export.active {
display: flex;
}
.abcode-dbe-last-export svg {
width: 16px;
height: 16px;
fill: #0f0d25;
flex-shrink: 0;
}
.abcode-dbe-format-select {
display: flex;
align-items: center;
gap: 12px;
}
.abcode-dbe-format-label {
font-size: 13px;
font-weight: 500;
color: #0f0d25;
}
.abcode-dbe-format-pills {
display: flex;
background: #f4f0fa;
border-radius: 8px;
padding: 3px;
gap: 2px;
}
.abcode-dbe-format-pill {
padding: 6px 16px;
border-radius: 6px;
border: none;
background: transparent;
font-size: 13px;
font-weight: 500;
color: #646970;
cursor: pointer;
transition: all 0.15s;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
letter-spacing: -0.3px;
}
.abcode-dbe-format-pill:hover {
color: #0f0d25;
}
.abcode-dbe-format-pill.active {
background: #0f0d25;
color: #fff;
box-shadow: 0 1px 3px rgba(15,13,37,0.2);
}
.abcode-dbe-format-pill.disabled,
.abcode-dbe-format-pill:disabled {
opacity: 0.35;
cursor: not-allowed;
}
.abcode-dbe-format-hint {
font-size: 12px;
color: #646970;
margin-left: auto;
}
.abcode-dbe-copied {
position: fixed;
bottom: 24px;
right: 24px;
background: #0f0d25;
color: #fff;
padding: 10px 18px;
border-radius: 6px;
font-size: 13px;
opacity: 0;
transform: translateY(8px);
transition: opacity 0.2s, transform 0.2s;
z-index: 99999;
pointer-events: none;
}
.abcode-dbe-copied.show {
opacity: 1;
transform: translateY(0);
}
(function() {
var downloadUrl = abcodeDbeConfig.downloadUrl;
var downloadNonce = abcodeDbeConfig.downloadNonce;
var ajaxUrl = abcodeDbeConfig.ajaxUrl;
var nonce = abcodeDbeConfig.nonce;
var i18n = abcodeDbeConfig.i18n;
// Table names come straight from the database and land in innerHTML,
// so they are escaped before being interpolated into markup.
function esc(str) {
return String(str).replace(/[&<>"']/g, function(c) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
});
}
function sprintf(fmt) {
var args = Array.prototype.slice.call(arguments, 1), i = 0;
return fmt.replace(/%(\d\$)?[sd]/g, function(m, pos) {
var idx = pos ? parseInt(pos) - 1 : i++;
return args[idx] !== undefined ? args[idx] : m;
});
}
function buildDownloadUrl(exportId, gzip) {
var url = downloadUrl + '&_wpnonce=' + encodeURIComponent(downloadNonce) + '&export_id=' + encodeURIComponent(exportId);
if (gzip) url += '&gzip=1';
return url;
}
function triggerDownload(url) {
var iframe = document.getElementById('abcode-dbe-dl-iframe');
if (!iframe) {
iframe = document.createElement('iframe');
iframe.id = 'abcode-dbe-dl-iframe';
iframe.style.display = 'none';
document.body.appendChild(iframe);
}
iframe.src = url;
}
var selectedFormat = 'sql';
var formatHints = { 'sql': i18n.standardSql, 'sql.gz': i18n.compressedSmaller };
var currentExportId = null;
var btn = document.getElementById('abcode-dbe-btn');
var btnText = document.getElementById('abcode-dbe-btn-text');
var progressWrap = document.getElementById('abcode-dbe-progress');
var bar = document.getElementById('abcode-dbe-bar');
var statusText = document.getElementById('abcode-dbe-status-text');
var statusPct = document.getElementById('abcode-dbe-status-pct');
var successMsg = document.getElementById('abcode-dbe-success');
var downloadArea = document.getElementById('abcode-dbe-download-area');
var tableData = [];
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
var k = 1024;
var sizes = ['B', 'KB', 'MB', 'GB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}
function formatNumber(n) {
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
function timeAgo(timestamp) {
var seconds = Math.floor(Date.now() / 1000) - timestamp;
if (seconds < 60) return i18n.justNow;
var minutes = Math.floor(seconds / 60);
if (minutes < 60) return sprintf(minutes !== 1 ? i18n.minutesAgo : i18n.minuteAgo, minutes);
var hours = Math.floor(minutes / 60);
if (hours < 24) return sprintf(hours !== 1 ? i18n.hoursAgo : i18n.hourAgo, hours);
var days = Math.floor(hours / 24);
return sprintf(days !== 1 ? i18n.daysAgo : i18n.dayAgo, days);
}
function showToast(msg) {
var toast = document.getElementById('abcode-dbe-copied');
toast.textContent = msg;
toast.classList.add('show');
setTimeout(function() { toast.classList.remove('show'); }, 2000);
}
function postAjax(action, data) {
var formData = new FormData();
formData.append('action', action);
formData.append('_nonce', nonce);
for (var key in data) {
formData.append(key, data[key]);
}
return fetch(ajaxUrl, {
method: 'POST',
credentials: 'same-origin',
body: formData,
}).then(function(r) { return r.json(); });
}
function showError(msg) {
statusText.textContent = sprintf(i18n.error, msg);
bar.style.background = '#d63638';
bar.style.width = '100%';
statusPct.textContent = '';
btn.disabled = false;
btnText.textContent = i18n.retryExport;
}
function findTableIndex(name) {
for (var i = 0; i < tableData.length; i++) {
if (tableData[i].name === name) return i;
}
return -1;
}
// Load DB info on page load
fetch(ajaxUrl + '?action=abcode_dbe_info&_nonce=' + encodeURIComponent(nonce), { credentials: 'same-origin' })
.then(function(r) { return r.json(); })
.then(function(resp) {
if (!resp.success) return;
var d = resp.data;
document.getElementById('abcode-dbe-db-name').textContent = d.db_name;
document.getElementById('abcode-dbe-db-name').classList.remove('loading');
document.getElementById('abcode-dbe-table-count').textContent = d.tables.length;
document.getElementById('abcode-dbe-table-count').classList.remove('loading');
document.getElementById('abcode-dbe-db-size').textContent = formatBytes(d.db_size);
document.getElementById('abcode-dbe-db-size').classList.remove('loading');
tableData = d.tables;
var rowsContainer = document.getElementById('abcode-dbe-table-rows');
rowsContainer.innerHTML = '';
// Show column header
var tableHead = document.getElementById('abcode-dbe-table-head');
tableHead.style.display = '';
var checkAll = document.getElementById('abcode-dbe-check-all');
d.tables.forEach(function(t, idx) {
var row = document.createElement('div');
row.className = 'abcode-dbe-table-row';
row.id = 'abcode-dbe-trow-' + idx;
row.setAttribute('data-table-name', t.name.toLowerCase());
var safeName = esc(t.name);
row.innerHTML = '' +
'' + safeName + '' +
'' + esc(formatNumber(t.rows)) + '' +
'' +
'' +
'' +
'' +
'' +
'';
rowsContainer.appendChild(row);
});
// Select all / none toggle
checkAll.addEventListener('change', function() {
var checked = this.checked;
document.querySelectorAll('.tbl-check').forEach(function(cb) {
cb.checked = checked;
});
updateBtnState();
});
// Update select-all checkbox and button when individual checkboxes change
rowsContainer.addEventListener('change', function(e) {
if (e.target.classList.contains('tbl-check')) {
var allBoxes = document.querySelectorAll('.tbl-check');
var checkedBoxes = document.querySelectorAll('.tbl-check:checked');
checkAll.checked = checkedBoxes.length === allBoxes.length;
checkAll.indeterminate = checkedBoxes.length > 0 && checkedBoxes.length < allBoxes.length;
updateBtnState();
}
});
function formatBtnLabel(count) {
var tablesLabel = sprintf(count !== 1 ? i18n.tablesPlural : i18n.tablesSingular, count);
return sprintf(i18n.exportBtn, tablesLabel);
}
function updateBtnState() {
var checkedCount = document.querySelectorAll('.tbl-check:checked').length;
if (checkedCount === 0) {
btn.disabled = true;
btnText.textContent = i18n.selectTables;
} else {
btn.disabled = false;
btnText.textContent = formatBtnLabel(checkedCount);
}
}
function updateBtnText() {
// No longer needed: format doesn't affect main button
}
// Individual table export buttons
rowsContainer.querySelectorAll('.abcode-dbe-table-dl').forEach(function(dlBtn) {
dlBtn.addEventListener('click', function() {
var tableName = this.getAttribute('data-table');
var thisBtn = this;
var rowIdx = findTableIndex(tableName);
thisBtn.disabled = true;
thisBtn.textContent = '...';
if (rowIdx !== -1) setRowState(rowIdx, 'exporting');
postAjax('abcode_dbe_single_table', { table: tableName }).then(function(resp) {
if (rowIdx !== -1) setRowState(rowIdx, null);
if (!resp.success) {
thisBtn.textContent = i18n.failed;
setTimeout(function() {
thisBtn.textContent = 'SQL';
thisBtn.disabled = false;
}, 2000);
return;
}
thisBtn.textContent = 'SQL';
thisBtn.disabled = false;
var dlUrl = buildDownloadUrl(resp.data.export_id, selectedFormat === 'sql.gz');
triggerDownload(dlUrl);
}).catch(function() {
if (rowIdx !== -1) setRowState(rowIdx, null);
thisBtn.textContent = i18n.failed;
setTimeout(function() {
thisBtn.textContent = 'SQL';
thisBtn.disabled = false;
}, 2000);
});
});
});
// Show search bar
document.getElementById('abcode-dbe-search').classList.add('active');
// Format pill selector
var formatPills = document.querySelectorAll('.abcode-dbe-format-pill');
var formatHint = document.getElementById('abcode-dbe-format-hint');
var dlBtn = document.getElementById('abcode-dbe-dl-link');
formatPills.forEach(function(pill) {
pill.addEventListener('click', function() {
formatPills.forEach(function(p) { p.classList.remove('active'); });
this.classList.add('active');
selectedFormat = this.getAttribute('data-format');
formatHint.textContent = formatHints[selectedFormat];
dlBtn.textContent = sprintf(i18n.downloadFormat, selectedFormat);
});
});
// Download button in success area
dlBtn.addEventListener('click', function() {
if (!currentExportId) return;
var url = buildDownloadUrl(currentExportId, selectedFormat === 'sql.gz');
triggerDownload(url);
});
// Search/filter tables
var searchInput = document.getElementById('abcode-dbe-search-input');
searchInput.addEventListener('input', function() {
var q = this.value.toLowerCase();
var rows = rowsContainer.querySelectorAll('.abcode-dbe-table-row');
rows.forEach(function(row) {
var name = row.getAttribute('data-table-name') || '';
row.style.display = (!q || name.indexOf(q) !== -1) ? '' : 'none';
});
});
// Schema copy buttons
rowsContainer.querySelectorAll('.abcode-dbe-table-schema').forEach(function(schemaBtn) {
schemaBtn.addEventListener('click', function() {
var tableName = this.getAttribute('data-table');
var thisBtn = this;
thisBtn.disabled = true;
thisBtn.textContent = '...';
postAjax('abcode_dbe_schema', { table: tableName }).then(function(resp) {
thisBtn.textContent = 'Schema';
thisBtn.disabled = false;
if (!resp.success) return;
navigator.clipboard.writeText(resp.data.sql).then(function() {
showToast(i18n.schemaCopied);
});
}).catch(function() {
thisBtn.textContent = 'Schema';
thisBtn.disabled = false;
});
});
});
// CSV export buttons
rowsContainer.querySelectorAll('.abcode-dbe-table-csv').forEach(function(csvBtn) {
csvBtn.addEventListener('click', function() {
var tableName = this.getAttribute('data-table');
var thisBtn = this;
thisBtn.disabled = true;
thisBtn.textContent = '...';
postAjax('abcode_dbe_csv', { table: tableName }).then(function(resp) {
thisBtn.textContent = 'CSV';
thisBtn.disabled = false;
if (!resp.success) return;
var dlUrl = buildDownloadUrl(resp.data.export_id);
triggerDownload(dlUrl);
}).catch(function() {
thisBtn.textContent = 'CSV';
thisBtn.disabled = false;
});
});
});
// Show last export timestamp
if (d.last_export > 0) {
var lastEl = document.getElementById('abcode-dbe-last-export');
var textEl = document.getElementById('abcode-dbe-last-export-text');
textEl.textContent = sprintf(i18n.lastExported, timeAgo(d.last_export));
lastEl.classList.add('active');
}
btn.disabled = false;
btnText.textContent = formatBtnLabel(d.tables.length);
});
function setRowState(idx, state) {
var row = document.getElementById('abcode-dbe-trow-' + idx);
if (!row) return;
row.classList.remove('exporting', 'exported');
if (state) row.classList.add(state);
}
function resetAllRows() {
for (var i = 0; i < tableData.length; i++) {
setRowState(i, null);
}
}
function setAllTableBtnsDisabled(disabled) {
document.querySelectorAll('.abcode-dbe-table-dl, .abcode-dbe-table-csv, .abcode-dbe-table-schema').forEach(function(b) {
b.disabled = disabled;
});
}
function getSelectedTables() {
var selected = [];
document.querySelectorAll('.tbl-check:checked').forEach(function(cb) {
var idx = parseInt(cb.getAttribute('data-idx'), 10);
selected.push({ idx: idx, name: tableData[idx].name });
});
return selected;
}
btn.addEventListener('click', function() {
// If export is complete, restart by reloading
if (currentExportId) {
window.location.reload();
return;
}
var selected = getSelectedTables();
if (selected.length === 0) return;
btn.disabled = true;
btnText.textContent = i18n.exporting;
document.getElementById('abcode-dbe-table-list').classList.add('locked');
progressWrap.classList.add('active');
successMsg.classList.remove('active');
downloadArea.classList.remove('active');
bar.style.background = '';
bar.style.width = '0%';
statusPct.textContent = '0%';
statusText.textContent = i18n.startingExport;
resetAllRows();
setAllTableBtnsDisabled(true);
var total = selected.length;
// Step 1: Start export
postAjax('abcode_dbe_start', {}).then(function(resp) {
if (!resp.success) {
showError(resp.data || i18n.failedStartExport);
setAllTableBtnsDisabled(false);
return;
}
var exportId = resp.data.export_id;
var current = 0;
// Step 2: Export selected tables one by one
function exportNextTable() {
if (current >= total) {
// Step 3: Finish export
statusText.textContent = i18n.finalisingExport;
bar.style.width = '98%';
statusPct.textContent = '98%';
postAjax('abcode_dbe_finish', { export_id: exportId }).then(function(resp) {
if (!resp.success) {
showError(resp.data || i18n.finalisingExport);
setAllTableBtnsDisabled(false);
return;
}
bar.style.width = '100%';
statusPct.textContent = '100%';
statusText.textContent = i18n.done;
// Update last export display
var lastEl = document.getElementById('abcode-dbe-last-export');
document.getElementById('abcode-dbe-last-export-text').textContent = sprintf(i18n.lastExported, i18n.justNow);
lastEl.classList.add('active');
// Store export ID for download
currentExportId = exportId;
// Show success bar and download area
successMsg.classList.add('active');
downloadArea.classList.add('active');
progressWrap.classList.remove('active');
// Lock table list
document.getElementById('abcode-dbe-table-list').classList.add('locked');
// Button becomes "Restart Export", which reloads the page
btn.disabled = false;
btnText.textContent = i18n.restartExport;
}).catch(function() {
showError(i18n.networkErrorFinalise);
setAllTableBtnsDisabled(false);
});
return;
}
var tbl = selected[current];
setRowState(tbl.idx, 'exporting');
statusText.textContent = sprintf(i18n.exportingTable, current + 1, total, tbl.name);
var pct = Math.round(((current + 1) / total) * 95);
bar.style.width = pct + '%';
statusPct.textContent = pct + '%';
postAjax('abcode_dbe_table', {
export_id: exportId,
table: tbl.name,
}).then(function(resp) {
if (!resp.success) {
showError(resp.data || sprintf(i18n.failedExportTable, tbl.name));
setAllTableBtnsDisabled(false);
return;
}
setRowState(tbl.idx, 'exported');
current++;
exportNextTable();
}).catch(function() {
showError(sprintf(i18n.networkErrorTable, tbl.name));
setAllTableBtnsDisabled(false);
});
}
exportNextTable();
}).catch(function() {
showError(i18n.networkErrorStart);
setAllTableBtnsDisabled(false);
});
});
// Keyboard shortcut: Ctrl+Enter or Cmd+Enter to start export
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && !btn.disabled) {
e.preventDefault();
btn.click();
}
});
})();
' . esc_html__('Export Database', 'abcode-database-exporter') . '');
return $links;
});
// ─── AJAX: get table list + row counts for progress tracking ──────────────────
add_action('wp_ajax_abcode_dbe_info', function () {
abcode_dbe_verify_request();
global $wpdb;
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- SHOW TABLES is uncacheable metadata.
$tables = $wpdb->get_col("SHOW TABLES");
$info = [];
$total_rows = 0;
// Use information_schema for fast row counts + DB size in a single query.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Live DB metadata, cannot be cached.
$table_stats = $wpdb->get_results(
$wpdb->prepare(
"SELECT table_name, table_rows, data_length + index_length AS size
FROM information_schema.tables
WHERE table_schema = %s",
DB_NAME
),
OBJECT_K
);
$db_size = 0;
foreach ($tables as $table) {
$rows = isset($table_stats[$table]) ? (int) $table_stats[$table]->table_rows : 0;
$info[] = ['name' => $table, 'rows' => $rows];
$total_rows += $rows;
if (isset($table_stats[$table])) {
$db_size += (int) $table_stats[$table]->size;
}
}
$last_export = get_option('abcode_dbe_last_export', 0);
wp_send_json_success([
'db_name' => DB_NAME,
'tables' => $info,
'total_rows' => $total_rows,
'db_size' => (int) $db_size,
'last_export' => (int) $last_export,
]);
});
// ─── Helpers ─────────────────────────────────────────────────────────────────
/**
* Returns the plugin's temp directory, creating and hardening it if needed.
*
* @return string Absolute path to the temp directory (no trailing slash).
*/
function abcode_dbe_get_temp_dir() {
$dir = wp_upload_dir();
$tmp = trailingslashit($dir['basedir']) . 'abcode-dbe-tmp';
if (!is_dir($tmp)) {
wp_mkdir_p($tmp);
// Block direct HTTP access on both Apache 2.2 and 2.4.
$htaccess = "