| 1 |
/** |
| 2 |
* Top 10 - WordPress Popular Posts Importer |
| 3 |
* |
| 4 |
* JavaScript for handling the WordPress Popular Posts import process. |
| 5 |
* |
| 6 |
* @package Top_Ten |
| 7 |
* @subpackage Admin/JS |
| 8 |
*/ |
| 9 |
|
| 10 |
/* global topTenWPPImporter */ |
| 11 |
jQuery(document).ready(function ($) { |
| 12 |
'use strict'; |
| 13 |
|
| 14 |
// Track if the import has been cancelled |
| 15 |
var importCancelled = false; |
| 16 |
|
| 17 |
/** |
| 18 |
* Escape HTML to prevent XSS attacks |
| 19 |
* |
| 20 |
* @param {string} unsafeText The unsafe text |
| 21 |
* @return {string} Escaped text |
| 22 |
*/ |
| 23 |
function escapeHTML(unsafeText) { |
| 24 |
return $('<div>').text(unsafeText).html(); |
| 25 |
} |
| 26 |
|
| 27 |
// Handle form submission. |
| 28 |
$('#top-ten-wpp-import-form').on('submit', function (e) { |
| 29 |
e.preventDefault(); |
| 30 |
|
| 31 |
// Confirm before proceeding. |
| 32 |
if (!confirm(topTenWPPImporter.strings.confirm)) { |
| 33 |
return; |
| 34 |
} |
| 35 |
|
| 36 |
var $form = $(this); |
| 37 |
var $submit = $('#top-ten-wpp-import-submit'); |
| 38 |
var $progress = $('#top-ten-wpp-import-progress'); |
| 39 |
var $results = $('#top-ten-wpp-import-results'); |
| 40 |
|
| 41 |
// Create cancel button if it doesn't exist |
| 42 |
var $cancelBtn = $('#top-ten-wpp-import-cancel'); |
| 43 |
if ($cancelBtn.length === 0) { |
| 44 |
$cancelBtn = $('<button>', { |
| 45 |
id: 'top-ten-wpp-import-cancel', |
| 46 |
class: 'button', |
| 47 |
text: topTenWPPImporter.strings.cancel_button, |
| 48 |
css: { 'margin-left': '10px' } |
| 49 |
}).insertAfter($submit); |
| 50 |
|
| 51 |
// Add click handler to cancel button |
| 52 |
$cancelBtn.on('click', function () { |
| 53 |
if (confirm(topTenWPPImporter.strings.cancel_confirm)) { |
| 54 |
importCancelled = true; |
| 55 |
$progress |
| 56 |
.removeClass('notice-info notice-success') |
| 57 |
.addClass('notice-error') |
| 58 |
.html('<strong>' + topTenWPPImporter.strings.import_cancelled + '</strong>'); |
| 59 |
$submit.prop('disabled', false); |
| 60 |
$(this).hide(); |
| 61 |
} |
| 62 |
return false; |
| 63 |
}); |
| 64 |
} |
| 65 |
|
| 66 |
// Reset cancelled flag |
| 67 |
importCancelled = false; |
| 68 |
|
| 69 |
// Disable submit button and show cancel button |
| 70 |
$submit.prop('disabled', true); |
| 71 |
$cancelBtn.show(); |
| 72 |
|
| 73 |
// Show progress |
| 74 |
$progress |
| 75 |
.removeClass('hidden notice-error notice-success') |
| 76 |
.addClass('notice-info') |
| 77 |
.html(topTenWPPImporter.strings.importing + |
| 78 |
' <span class="import-status">' + topTenWPPImporter.strings.starting + '</span><br>' + |
| 79 |
'<div class="import-progress-bar" style="background-color: #f5f5f5; border: 1px solid #ddd; height: 20px; margin: 10px 0;">' + |
| 80 |
'<div style="width: 0%; height: 100%; background-color: #0073aa;"></div></div>' + |
| 81 |
'<span class="import-percentage">0%</span>'); |
| 82 |
|
| 83 |
$results.addClass('hidden'); |
| 84 |
|
| 85 |
// Get selected sites for multisite. |
| 86 |
var selectedSites = []; |
| 87 |
var isNetworkAdmin = $('input[name="sites[]"]').length > 0; |
| 88 |
|
| 89 |
// Only collect checked sites if we're in network admin |
| 90 |
if (isNetworkAdmin) { |
| 91 |
$('input[name="sites[]"]:checked').each(function () { |
| 92 |
selectedSites.push($(this).val()); |
| 93 |
}); |
| 94 |
|
| 95 |
// Validate that at least one site is selected in network admin |
| 96 |
if (selectedSites.length === 0) { |
| 97 |
$progress |
| 98 |
.removeClass('hidden notice-info notice-success') |
| 99 |
.addClass('notice-error') |
| 100 |
.html('<strong>' + topTenWPPImporter.strings.error + '</strong> ' + topTenWPPImporter.strings.no_sites_selected); |
| 101 |
$submit.prop('disabled', false); |
| 102 |
return false; |
| 103 |
} |
| 104 |
} |
| 105 |
|
| 106 |
// Validate minimum views input |
| 107 |
var minViews = $('input[name="min_views"]').val(); |
| 108 |
if (minViews !== '' && (!$.isNumeric(minViews) || parseInt(minViews) < 0)) { |
| 109 |
$progress |
| 110 |
.removeClass('hidden notice-info notice-success') |
| 111 |
.addClass('notice-error') |
| 112 |
.html('<strong>' + topTenWPPImporter.strings.error + '</strong> ' + topTenWPPImporter.strings.invalid_min_views); |
| 113 |
$submit.prop('disabled', false); |
| 114 |
return false; |
| 115 |
} |
| 116 |
|
| 117 |
// Store the form parameters for batch processing |
| 118 |
var importParams = { |
| 119 |
action: 'top_ten_import_wpp', |
| 120 |
nonce: topTenWPPImporter.nonce, |
| 121 |
import_mode: $('input[name="import_mode"]:checked').val(), |
| 122 |
import_data: $('input[name="import_data"]:checked').val(), |
| 123 |
min_views: $('input[name="min_views"]').val(), |
| 124 |
dry_run: $('input[name="dry_run"]').is(':checked') ? 1 : 0, |
| 125 |
is_network_admin: isNetworkAdmin ? 1 : 0, |
| 126 |
batch: true, |
| 127 |
sites: selectedSites, |
| 128 |
currentSiteIndex: 0, |
| 129 |
batch_number: 1, |
| 130 |
total_batches: 0, |
| 131 |
results: {} |
| 132 |
}; |
| 133 |
|
| 134 |
// Start the batch process |
| 135 |
processBatch(importParams, $progress, $results, $submit); |
| 136 |
}); |
| 137 |
|
| 138 |
/** |
| 139 |
* Update the progress bar with current percentage |
| 140 |
* |
| 141 |
* @param {jQuery} $progress The progress element |
| 142 |
* @param {number} current Current step or batch |
| 143 |
* @param {number} total Total steps or batches |
| 144 |
*/ |
| 145 |
function updateProgressBar($progress, current, total) { |
| 146 |
// Calculate percentage, with a minimum of 5% to show activity |
| 147 |
var percentage = total > 0 ? Math.min(Math.round((current / total) * 100), 100) : 5; |
| 148 |
|
| 149 |
// Update the progress bar |
| 150 |
$progress.find('.import-progress-bar div').css('width', percentage + '%'); |
| 151 |
$progress.find('.import-percentage').text(percentage + '%'); |
| 152 |
} |
| 153 |
|
| 154 |
/** |
| 155 |
* Process a single batch of the import |
| 156 |
* |
| 157 |
* @param {Object} params The import parameters |
| 158 |
* @param {jQuery} $progress The progress element |
| 159 |
* @param {jQuery} $results The results element |
| 160 |
* @param {jQuery} $submit The submit button |
| 161 |
*/ |
| 162 |
function processBatch(params, $progress, $results, $submit) { |
| 163 |
// Check if import has been cancelled |
| 164 |
if (importCancelled) { |
| 165 |
$('#top-ten-wpp-import-cancel').hide(); |
| 166 |
$submit.prop('disabled', false); |
| 167 |
return; |
| 168 |
} |
| 169 |
|
| 170 |
// If we've processed all sites, we're done |
| 171 |
if (params.currentSiteIndex >= params.sites.length && params.sites.length > 0) { |
| 172 |
// Show final results |
| 173 |
showFinalResults(params, $progress, $results, $submit); |
| 174 |
return; |
| 175 |
} |
| 176 |
|
| 177 |
// Current site being processed |
| 178 |
var currentSite = params.sites.length ? params.sites[params.currentSiteIndex] : 0; |
| 179 |
|
| 180 |
// Update the progress indicator |
| 181 |
$progress.find('.import-status').html( |
| 182 |
(params.sites.length > 1 ? topTenWPPImporter.strings.processing_site + ' ' + currentSite + ' - ' : '') + |
| 183 |
topTenWPPImporter.strings.batch + ' ' + params.batch_number + |
| 184 |
(params.total_batches > 0 ? ' ' + topTenWPPImporter.strings.of + ' ' + params.total_batches : '') |
| 185 |
); |
| 186 |
|
| 187 |
// Calculate overall progress based on sites and batches |
| 188 |
var currentSiteIndex = params.currentSiteIndex; |
| 189 |
var totalSites = params.sites.length; |
| 190 |
var currentBatch = params.batch_number; |
| 191 |
var totalBatches = params.total_batches; |
| 192 |
|
| 193 |
// If we know the total batches, use more precise calculation |
| 194 |
if (totalBatches > 0) { |
| 195 |
var overallProgress = ((currentSiteIndex * totalBatches) + currentBatch) / (totalSites * totalBatches); |
| 196 |
updateProgressBar($progress, overallProgress * 100, 100); |
| 197 |
} else { |
| 198 |
// Simple calculation based on sites processed |
| 199 |
updateProgressBar($progress, currentSiteIndex + 1, totalSites); |
| 200 |
} |
| 201 |
|
| 202 |
// Debug info to help diagnose multisite issues |
| 203 |
/* console.log('Processing batch with params:', { |
| 204 |
currentSite: currentSite, |
| 205 |
isNetworkAdmin: params.is_network_admin, |
| 206 |
selectedSites: params.sites, |
| 207 |
batchNumber: params.batch_number |
| 208 |
}); */ |
| 209 |
|
| 210 |
// Send AJAX request for this batch |
| 211 |
$.ajax({ |
| 212 |
url: topTenWPPImporter.ajaxurl, |
| 213 |
type: 'POST', |
| 214 |
timeout: 60000, // 60 seconds timeout |
| 215 |
data: { |
| 216 |
action: 'top_ten_import_wpp', |
| 217 |
nonce: topTenWPPImporter.nonce, |
| 218 |
import_mode: params.import_mode, |
| 219 |
import_data: params.import_data, |
| 220 |
min_views: params.min_views, |
| 221 |
dry_run: params.dry_run, |
| 222 |
batch: true, |
| 223 |
blog_id: currentSite, |
| 224 |
batch_number: params.batch_number, |
| 225 |
is_network_admin: params.is_network_admin, |
| 226 |
sites: params.sites // Add sites array to fix multisite selection |
| 227 |
}, |
| 228 |
success: function (response) { |
| 229 |
if (response.success) { |
| 230 |
// Handle batch response |
| 231 |
if (response.data.batch_status) { |
| 232 |
// Store batch results for the current site |
| 233 |
if (!params.results[currentSite]) { |
| 234 |
params.results[currentSite] = { |
| 235 |
posts_processed: 0, |
| 236 |
total_counts: 0, |
| 237 |
total_views_imported: 0, |
| 238 |
daily_counts: 0, |
| 239 |
daily_views_imported: 0, |
| 240 |
errors: [] |
| 241 |
}; |
| 242 |
} |
| 243 |
|
| 244 |
// Update the batch status |
| 245 |
params.total_batches = response.data.total_batches || params.total_batches; |
| 246 |
|
| 247 |
// Update the progress percentage |
| 248 |
var currentSiteIndex = params.currentSiteIndex; |
| 249 |
var totalSites = params.sites.length; |
| 250 |
var currentBatch = params.batch_number; |
| 251 |
var totalBatches = params.total_batches; |
| 252 |
|
| 253 |
if (totalBatches > 0) { |
| 254 |
var overallProgress = ((currentSiteIndex * totalBatches) + currentBatch) / (totalSites * totalBatches); |
| 255 |
updateProgressBar($progress, overallProgress * 100, 100); |
| 256 |
} |
| 257 |
|
| 258 |
// Increment stats from this batch |
| 259 |
if (response.data.batch_results) { |
| 260 |
var batchResults = response.data.batch_results; |
| 261 |
params.results[currentSite].posts_processed += parseInt(batchResults.posts_processed || 0); |
| 262 |
params.results[currentSite].total_counts += parseInt(batchResults.total_counts || 0); |
| 263 |
params.results[currentSite].total_views_imported += parseInt(batchResults.total_views_imported || 0); |
| 264 |
params.results[currentSite].daily_counts += parseInt(batchResults.daily_counts || 0); |
| 265 |
params.results[currentSite].daily_views_imported += parseInt(batchResults.daily_views_imported || 0); |
| 266 |
|
| 267 |
// Add any errors |
| 268 |
if (batchResults.errors && batchResults.errors.length) { |
| 269 |
params.results[currentSite].errors = |
| 270 |
params.results[currentSite].errors.concat(batchResults.errors); |
| 271 |
} |
| 272 |
} |
| 273 |
|
| 274 |
// If we have more batches for this site |
| 275 |
if (response.data.has_more_batches) { |
| 276 |
params.batch_number++; |
| 277 |
// Process the next batch for this site |
| 278 |
processBatch(params, $progress, $results, $submit); |
| 279 |
return; |
| 280 |
} else { |
| 281 |
// Move to the next site |
| 282 |
params.currentSiteIndex++; |
| 283 |
params.batch_number = 1; |
| 284 |
// Process the first batch of the next site |
| 285 |
processBatch(params, $progress, $results, $submit); |
| 286 |
return; |
| 287 |
} |
| 288 |
} else { |
| 289 |
// This is the final success response - for compatibility with non-batch responses |
| 290 |
// Process the response.data.results if available |
| 291 |
if (response.data.results) { |
| 292 |
var results = response.data.results; |
| 293 |
|
| 294 |
// Add these results to our params.results |
| 295 |
$.each(results, function (blogId, result) { |
| 296 |
params.results[blogId] = result; |
| 297 |
}); |
| 298 |
} |
| 299 |
|
| 300 |
// Show final results |
| 301 |
showFinalResults(params, $progress, $results, $submit); |
| 302 |
} |
| 303 |
} else { |
| 304 |
// Properly handle server-side errors |
| 305 |
var errorMessage = response.data && response.data.message |
| 306 |
? response.data.message |
| 307 |
: topTenWPPImporter.strings.unknown_error; |
| 308 |
handleError(errorMessage); |
| 309 |
} |
| 310 |
}, |
| 311 |
error: function (xhr, status, error) { |
| 312 |
if (status === 'timeout') { |
| 313 |
handleError(topTenWPPImporter.strings.timeout_error); |
| 314 |
} else { |
| 315 |
var errorMessage = topTenWPPImporter.strings.server_error + ' ' + (xhr.responseJSON && xhr.responseJSON.message |
| 316 |
? xhr.responseJSON.message |
| 317 |
: error); |
| 318 |
handleError(errorMessage); |
| 319 |
} |
| 320 |
} |
| 321 |
}); |
| 322 |
} |
| 323 |
|
| 324 |
/** |
| 325 |
* Handle error display. |
| 326 |
* |
| 327 |
* @param {string} error Error message to display. |
| 328 |
*/ |
| 329 |
function handleError(error) { |
| 330 |
var $progress = $('#top-ten-wpp-import-progress'); |
| 331 |
var $results = $('#top-ten-wpp-import-results'); |
| 332 |
var $submit = $('#top-ten-wpp-import-submit'); |
| 333 |
var $cancelBtn = $('#top-ten-wpp-import-cancel'); |
| 334 |
|
| 335 |
$progress |
| 336 |
.removeClass('hidden notice-info notice-success') |
| 337 |
.addClass('notice-error') |
| 338 |
.html('<strong>' + topTenWPPImporter.strings.import_error + '</strong>'); |
| 339 |
|
| 340 |
$results |
| 341 |
.removeClass('hidden notice-success') |
| 342 |
.addClass('notice-error') |
| 343 |
.html(topTenWPPImporter.strings.import_error + '<br><br>' + escapeHTML(error)); |
| 344 |
|
| 345 |
// Re-enable the submit button and hide cancel button |
| 346 |
$submit.prop('disabled', false); |
| 347 |
$cancelBtn.hide(); |
| 348 |
} |
| 349 |
|
| 350 |
/** |
| 351 |
* Display the final results after all batches are complete |
| 352 |
* |
| 353 |
* @param {Object} params The import parameters |
| 354 |
* @param {jQuery} $progress The progress element |
| 355 |
* @param {jQuery} $results The results element |
| 356 |
* @param {jQuery} $submit The submit button |
| 357 |
*/ |
| 358 |
function showFinalResults(params, $progress, $results, $submit) { |
| 359 |
// Enable the submit button and hide cancel button |
| 360 |
$submit.prop('disabled', false); |
| 361 |
$('#top-ten-wpp-import-cancel').hide(); |
| 362 |
|
| 363 |
// Update progress status |
| 364 |
$progress |
| 365 |
.removeClass('notice-info') |
| 366 |
.addClass('notice-success') |
| 367 |
.removeClass('hidden') |
| 368 |
.html('<strong>' + topTenWPPImporter.strings.import_complete + '</strong>'); |
| 369 |
|
| 370 |
// Prepare to show results |
| 371 |
$results |
| 372 |
.removeClass('hidden notice-error') |
| 373 |
.addClass('notice-success'); |
| 374 |
|
| 375 |
var message = '<br />'; |
| 376 |
var isDryRun = params.dry_run ? true : false; |
| 377 |
var runType = isDryRun ? topTenWPPImporter.strings.dry_run + ' ' : ''; |
| 378 |
// Calculate the number of sites processed - use the number of keys in the results object |
| 379 |
var sitesProcessed = Object.keys(params.results).length; |
| 380 |
|
| 381 |
// If we have a multisite setup with multiple sites |
| 382 |
if (Object.keys(params.results).length > 1) { |
| 383 |
// Multi-site results |
| 384 |
message += '<strong>' + runType + topTenWPPImporter.strings.sites_processed + ' ' + sitesProcessed + '</strong><br><br>'; |
| 385 |
|
| 386 |
$.each(params.results, function (blogId, result) { |
| 387 |
message += '<strong>' + topTenWPPImporter.strings.blog_id + ' ' + blogId + ':</strong><br>'; |
| 388 |
message += topTenWPPImporter.strings.posts_processed + ' ' + (result.posts_processed || 0) + '<br>'; |
| 389 |
message += topTenWPPImporter.strings.total_records + ' ' + (result.total_counts || 0) + '<br>'; |
| 390 |
message += topTenWPPImporter.strings.total_views_found + ' ' + (result.total_views_imported || 0) + '<br>'; |
| 391 |
message += topTenWPPImporter.strings.daily_records + ' ' + (result.daily_counts || 0) + '<br>'; |
| 392 |
message += topTenWPPImporter.strings.daily_views_found + ' ' + (result.daily_views_imported || 0) + '<br>'; |
| 393 |
|
| 394 |
if (result.errors && result.errors.length) { |
| 395 |
message += '<strong>' + topTenWPPImporter.strings.errors + '</strong><br>'; |
| 396 |
$.each(result.errors, function (i, error) { |
| 397 |
message += '- ' + escapeHTML(error) + '<br>'; |
| 398 |
}); |
| 399 |
} |
| 400 |
message += '<br>'; |
| 401 |
}); |
| 402 |
} else { |
| 403 |
// Single site result |
| 404 |
var siteId = Object.keys(params.results)[0] || 0; |
| 405 |
var results = params.results[siteId] || { |
| 406 |
posts_processed: 0, |
| 407 |
total_counts: 0, |
| 408 |
total_views_imported: 0, |
| 409 |
daily_counts: 0, |
| 410 |
daily_views_imported: 0, |
| 411 |
errors: [] |
| 412 |
}; |
| 413 |
|
| 414 |
message += '<strong>' + runType + topTenWPPImporter.strings.results + '</strong><br>'; |
| 415 |
message += topTenWPPImporter.strings.posts_processed + ' ' + (results.posts_processed || 0) + '<br>'; |
| 416 |
message += topTenWPPImporter.strings.total_records + ' ' + (results.total_counts || 0) + '<br>'; |
| 417 |
message += topTenWPPImporter.strings.total_views_found + ' ' + (results.total_views_imported || 0) + '<br>'; |
| 418 |
message += topTenWPPImporter.strings.daily_records + ' ' + (results.daily_counts || 0) + '<br>'; |
| 419 |
message += topTenWPPImporter.strings.daily_views_found + ' ' + (results.daily_views_imported || 0) + '<br>'; |
| 420 |
|
| 421 |
if (results.errors && results.errors.length) { |
| 422 |
message += '<strong>' + topTenWPPImporter.strings.errors + '</strong><br>'; |
| 423 |
$.each(results.errors, function (i, error) { |
| 424 |
message += '- ' + escapeHTML(error) + '<br>'; |
| 425 |
}); |
| 426 |
} |
| 427 |
} |
| 428 |
|
| 429 |
// Display the results |
| 430 |
$results.html(message); |
| 431 |
} |
| 432 |
}); |
| 433 |
|