|
|
@@ -19,7 +19,7 @@
|
|
|
// Initialize admin scripts
|
|
|
$(document).ready(function() {
|
|
|
// Debug: Log that script is loaded
|
|
|
- console.log('STUDIOU WC: Admin script loaded v1.6.4');
|
|
|
+ console.log('STUDIOU WC: Admin script loaded v1.7.0');
|
|
|
|
|
|
if (typeof studiouWcpcm === 'undefined') {
|
|
|
console.error('STUDIOU WC: studiouWcpcm object is NOT defined - AJAX will not work');
|
|
|
@@ -163,25 +163,29 @@
|
|
|
* Initialize import form
|
|
|
*/
|
|
|
function initImportForm() {
|
|
|
+ // v1.7.0 — M1: batched category import. The flow is parse → loop
|
|
|
+ // batches → summarise, mirroring the product importer. Removes the
|
|
|
+ // single-shot ~60s timeout on large category trees. The legacy
|
|
|
+ // single-shot endpoint stays registered for back-compat but JS no
|
|
|
+ // longer calls it.
|
|
|
$('#studiou-wcpcm-import-form').on('submit', function(e) {
|
|
|
e.preventDefault();
|
|
|
-
|
|
|
- // Show spinner
|
|
|
- var $submitBtn = $(this).find('#studiou-wcpcm-import-button');
|
|
|
- var $spinner = $(this).find('.spinner');
|
|
|
-
|
|
|
+
|
|
|
+ var $form = $(this);
|
|
|
+ var $submitBtn = $form.find('#studiou-wcpcm-import-button');
|
|
|
+ var $spinner = $form.find('.spinner');
|
|
|
+
|
|
|
$submitBtn.prop('disabled', true);
|
|
|
$spinner.css('visibility', 'visible');
|
|
|
-
|
|
|
- // Clear previous notices
|
|
|
$('.studiou-wcpcm-notice-area').empty();
|
|
|
-
|
|
|
- // Create form data
|
|
|
+ $('.studiou-wcpcm-import-results').hide();
|
|
|
+ $('#studiou-wcpcm-import-results').empty();
|
|
|
+
|
|
|
+ // Step 1 — parse the CSV server-side, stage to disk, get a token.
|
|
|
var formData = new FormData(this);
|
|
|
- formData.append('action', 'studiou_wcpcm_import');
|
|
|
+ formData.append('action', 'studiou_wcpcm_import_parse');
|
|
|
formData.append('nonce', studiouWcpcm.nonce);
|
|
|
-
|
|
|
- // Send AJAX request
|
|
|
+
|
|
|
$.ajax({
|
|
|
url: studiouWcpcm.ajaxUrl,
|
|
|
type: 'POST',
|
|
|
@@ -189,26 +193,84 @@
|
|
|
processData: false,
|
|
|
contentType: false,
|
|
|
success: function(response) {
|
|
|
- if (response.success) {
|
|
|
- showNotice('success', studiouWcpcm.i18n.importSuccess);
|
|
|
-
|
|
|
- // Show results
|
|
|
- $('#studiou-wcpcm-import-results').text(response.data.message);
|
|
|
- $('.studiou-wcpcm-import-results').show();
|
|
|
- } else {
|
|
|
- showNotice('error', response.data.message);
|
|
|
+ if (!response.success) {
|
|
|
+ showNotice('error', response.data && response.data.message ? response.data.message : 'Error');
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ return;
|
|
|
}
|
|
|
+ runImportBatches(response.data.staging_key, response.data.total, $submitBtn, $spinner);
|
|
|
},
|
|
|
error: function() {
|
|
|
- showNotice('error', studiouWcpcm.i18n.importError);
|
|
|
- },
|
|
|
- complete: function() {
|
|
|
- // Hide spinner
|
|
|
+ showNotice('error', studiouWcpcm.i18n.importError || 'Import error');
|
|
|
$submitBtn.prop('disabled', false);
|
|
|
$spinner.css('visibility', 'hidden');
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+ function runImportBatches(stagingKey, total, $submitBtn, $spinner) {
|
|
|
+ var processed = 0;
|
|
|
+ var totalSuccess = 0;
|
|
|
+ var totalErrors = 0;
|
|
|
+ var allMessages = [];
|
|
|
+
|
|
|
+ // Show or build a simple progress indicator. The view doesn't
|
|
|
+ // require a dedicated progress-bar element; we render into the
|
|
|
+ // existing results container.
|
|
|
+ var $results = $('#studiou-wcpcm-import-results');
|
|
|
+ $results.empty();
|
|
|
+ $('.studiou-wcpcm-import-results').show();
|
|
|
+ $results.html('<p>0 / ' + total + '</p>');
|
|
|
+
|
|
|
+ function nextBatch() {
|
|
|
+ $.ajax({
|
|
|
+ url: studiouWcpcm.ajaxUrl,
|
|
|
+ type: 'POST',
|
|
|
+ data: {
|
|
|
+ action: 'studiou_wcpcm_import_batch',
|
|
|
+ nonce: studiouWcpcm.nonce,
|
|
|
+ staging_key: stagingKey,
|
|
|
+ offset: processed
|
|
|
+ },
|
|
|
+ success: function(response) {
|
|
|
+ if (!response.success) {
|
|
|
+ showNotice('error', response.data && response.data.message ? response.data.message : 'Error');
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ processed = response.data.processed;
|
|
|
+ totalSuccess += response.data.success || 0;
|
|
|
+ totalErrors += response.data.errors || 0;
|
|
|
+ if (Array.isArray(response.data.messages)) {
|
|
|
+ allMessages = allMessages.concat(response.data.messages);
|
|
|
+ }
|
|
|
+ $results.html('<p>' + processed + ' / ' + total + '</p>');
|
|
|
+
|
|
|
+ if (response.data.done) {
|
|
|
+ var summary = '<p><strong>' + (totalSuccess + totalErrors) + ' items processed: ' + totalSuccess + ' OK, ' + totalErrors + ' errors.</strong></p>';
|
|
|
+ if (allMessages.length) {
|
|
|
+ summary += '<pre style="max-height:300px;overflow:auto;">' + allMessages.map(function(m){return $('<div>').text(m).html();}).join('\n') + '</pre>';
|
|
|
+ }
|
|
|
+ $results.html(summary);
|
|
|
+ showNotice(totalErrors === 0 ? 'success' : 'warning', studiouWcpcm.i18n.importSuccess || 'Import complete');
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ } else {
|
|
|
+ nextBatch();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: function() {
|
|
|
+ showNotice('error', studiouWcpcm.i18n.importError || 'Import error');
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ nextBatch();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -264,91 +326,130 @@
|
|
|
* Initialize move products form
|
|
|
*/
|
|
|
function initMoveForm() {
|
|
|
- console.log('STUDIOU WC: Initializing move form handlers');
|
|
|
-
|
|
|
- // Add debug button click handler first
|
|
|
- $('#studiou-wcpcm-move-button').on('click', function(e) {
|
|
|
- console.log('STUDIOU WC: Move button clicked directly');
|
|
|
- });
|
|
|
-
|
|
|
+ // v1.7.0 — M2: batched product move. Step 1 (start) returns the
|
|
|
+ // list of product IDs in the source category. Step 2 (chunk) moves
|
|
|
+ // 25 at a time. JS drives the chunking + progress bar. Removes the
|
|
|
+ // single-shot ~60s PHP-timeout cliff on large source categories.
|
|
|
$('#studiou-wcpcm-move-form').on('submit', function(e) {
|
|
|
e.preventDefault();
|
|
|
-
|
|
|
- // Debug: Log form submission
|
|
|
- console.log('STUDIOU WC: Move form submitted');
|
|
|
-
|
|
|
- // Validate that different categories are selected
|
|
|
+
|
|
|
var sourceCategory = $('#source_category').val();
|
|
|
var targetCategory = $('#target_category').val();
|
|
|
-
|
|
|
- console.log('STUDIOU WC: Source category:', sourceCategory, 'Target category:', targetCategory);
|
|
|
-
|
|
|
+
|
|
|
if (!sourceCategory || !targetCategory) {
|
|
|
- console.log('STUDIOU WC: Missing category selection');
|
|
|
- showNotice('error', 'Please select both source and target categories');
|
|
|
+ showNotice('error', studiouWcpcm.i18n.selectBothCategories || 'Please select both source and target categories');
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
if (sourceCategory === targetCategory) {
|
|
|
- console.log('STUDIOU WC: Same category selected');
|
|
|
showNotice('error', studiouWcpcm.i18n.selectDifferentCategories);
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
- // Show spinner
|
|
|
- var $submitBtn = $(this).find('#studiou-wcpcm-move-button');
|
|
|
- var $spinner = $(this).find('.spinner');
|
|
|
-
|
|
|
+
|
|
|
+ var $form = $(this);
|
|
|
+ var $submitBtn = $form.find('#studiou-wcpcm-move-button');
|
|
|
+ var $spinner = $form.find('.spinner');
|
|
|
$submitBtn.prop('disabled', true);
|
|
|
$spinner.css('visibility', 'visible');
|
|
|
-
|
|
|
- // Clear previous notices
|
|
|
$('.studiou-wcpcm-notice-area').empty();
|
|
|
-
|
|
|
- // Debug: Log AJAX request details
|
|
|
- console.log('STUDIOU WC: Sending AJAX request to:', studiouWcpcm.ajaxUrl);
|
|
|
- console.log('STUDIOU WC: Nonce:', studiouWcpcm.nonce);
|
|
|
-
|
|
|
- // Send AJAX request
|
|
|
+ $('.studiou-wcpcm-move-results').hide();
|
|
|
+
|
|
|
+ // Step 1 — get the product IDs to move.
|
|
|
$.ajax({
|
|
|
url: studiouWcpcm.ajaxUrl,
|
|
|
type: 'POST',
|
|
|
data: {
|
|
|
- action: 'studiou_wcpcm_move_products',
|
|
|
+ action: 'studiou_wcpcm_move_products_start',
|
|
|
source_category: sourceCategory,
|
|
|
target_category: targetCategory,
|
|
|
nonce: studiouWcpcm.nonce
|
|
|
},
|
|
|
success: function(response) {
|
|
|
- console.log('STUDIOU WC: AJAX response:', response);
|
|
|
-
|
|
|
- if (response.success) {
|
|
|
- showNotice('success', studiouWcpcm.i18n.moveSuccess);
|
|
|
-
|
|
|
- // Show results
|
|
|
- $('#studiou-wcpcm-move-message').html('<p>' + response.data.message + '</p>');
|
|
|
+ if (!response.success) {
|
|
|
+ showNotice('error', response.data && response.data.message ? response.data.message : (studiouWcpcm.i18n.moveError || 'Error'));
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ var ids = response.data.product_ids || [];
|
|
|
+ var total = response.data.total || 0;
|
|
|
+ var sourceName = response.data.source_name || '';
|
|
|
+ var targetName = response.data.target_name || '';
|
|
|
+
|
|
|
+ if (total === 0) {
|
|
|
+ $('#studiou-wcpcm-move-message').html('<p>' + (studiouWcpcm.i18n.moveNoProducts || 'No products to move.') + '</p>');
|
|
|
$('.studiou-wcpcm-move-results').show();
|
|
|
-
|
|
|
- // Refresh the page to update category counts
|
|
|
- setTimeout(function() {
|
|
|
- location.reload();
|
|
|
- }, 3000);
|
|
|
- } else {
|
|
|
- showNotice('error', response.data.message);
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ return;
|
|
|
}
|
|
|
+ runMoveChunks(ids, total, sourceCategory, targetCategory, sourceName, targetName, $submitBtn, $spinner);
|
|
|
},
|
|
|
- error: function(xhr, status, error) {
|
|
|
- console.error('STUDIOU WC: AJAX error:', status, error);
|
|
|
- console.error('STUDIOU WC: Response text:', xhr.responseText);
|
|
|
- showNotice('error', studiouWcpcm.i18n.moveError + ': ' + error);
|
|
|
- },
|
|
|
- complete: function() {
|
|
|
- // Hide spinner
|
|
|
+ error: function() {
|
|
|
+ showNotice('error', studiouWcpcm.i18n.moveError || 'Move error');
|
|
|
$submitBtn.prop('disabled', false);
|
|
|
$spinner.css('visibility', 'hidden');
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
+
|
|
|
+ function runMoveChunks(allIds, total, sourceId, targetId, sourceName, targetName, $submitBtn, $spinner) {
|
|
|
+ var CHUNK = 25;
|
|
|
+ var idx = 0;
|
|
|
+ var movedTotal = 0;
|
|
|
+ var failedTotal = 0;
|
|
|
+
|
|
|
+ var $msg = $('#studiou-wcpcm-move-message');
|
|
|
+ $msg.html('<p>0 / ' + total + '</p>');
|
|
|
+ $('.studiou-wcpcm-move-results').show();
|
|
|
+
|
|
|
+ function nextChunk() {
|
|
|
+ var chunk = allIds.slice(idx, idx + CHUNK);
|
|
|
+ if (chunk.length === 0) { return; }
|
|
|
+
|
|
|
+ $.ajax({
|
|
|
+ url: studiouWcpcm.ajaxUrl,
|
|
|
+ type: 'POST',
|
|
|
+ data: {
|
|
|
+ action: 'studiou_wcpcm_move_products_chunk',
|
|
|
+ nonce: studiouWcpcm.nonce,
|
|
|
+ source_category: sourceId,
|
|
|
+ target_category: targetId,
|
|
|
+ product_ids: chunk
|
|
|
+ },
|
|
|
+ success: function(response) {
|
|
|
+ if (!response.success) {
|
|
|
+ showNotice('error', response.data && response.data.message ? response.data.message : (studiouWcpcm.i18n.moveError || 'Error'));
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ movedTotal += response.data.moved || 0;
|
|
|
+ failedTotal += response.data.failed || 0;
|
|
|
+ idx += chunk.length;
|
|
|
+ $msg.html('<p>' + idx + ' / ' + total + '</p>');
|
|
|
+ if (idx >= total) {
|
|
|
+ var summary = '<p>' + movedTotal + ' moved from "' + sourceName + '" to "' + targetName + '"';
|
|
|
+ if (failedTotal > 0) { summary += ', ' + failedTotal + ' failed'; }
|
|
|
+ summary += '.</p>';
|
|
|
+ $msg.html(summary);
|
|
|
+ showNotice(failedTotal === 0 ? 'success' : 'warning', studiouWcpcm.i18n.moveSuccess || 'Move complete');
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ setTimeout(function() { location.reload(); }, 2000);
|
|
|
+ } else {
|
|
|
+ nextChunk();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ error: function() {
|
|
|
+ showNotice('error', studiouWcpcm.i18n.moveError || 'Move error');
|
|
|
+ $submitBtn.prop('disabled', false);
|
|
|
+ $spinner.css('visibility', 'hidden');
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ nextChunk();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|