1/* Remove legacy ?single_login= from URL ASAP (some themes/plugins append it after forced logout â old JS showed a misleading popup). */ 2(function () { 3 try { 4 if (!window.location.search || window.location.search.indexOf('single_login') === -1) { 5 return; 6 } 7 var url = new URL(window.location.href); 8 url.searchParams.delete('single_login'); 9 var q = url.searchParams.toString(); 10 var path = url.pathname + (q ? '?' + q : '') + url.hash; 11 window.history.replaceState({}, '', path); 12 } catch (ignore) {} 13})(); 14 15var $checker_limit = params['limit']; 16/* `$chunk_size` is the MAX URLs per upstream POST (premium=500 / free=10). 17 * The INPUT cap (`$checker_limit`) may be larger (default 700) â the server 18 * splits into chunk-sized batches. Default premium: whole Check = 1 request. 19 * Custom URL-cap premium (>700): requests = ceil(urls / billingUnit). */ 20var $chunk_size = parseInt(params['chunkSize'] || 0, 10) || (params['isPremium'] ? 500 : 10); 21var $is_premium = !!params['isPremium']; 22var $is_logged_in = !!params['isLoggedIn']; 23var $request_cap = parseInt(params['requestCap'] || 0, 10) || 0; 24var $request_used = parseInt(params['requestUsed'] || 0, 10) || 0; 25var $request_remaining = parseInt(params['requestRemaining'] || 0, 10) || 0; 26var $lifetime_urls = parseInt(params['lifetimeUrls'] || 0, 10) || 0; 27var $upgrade_url = params['upgradeUrl'] || 'https://dapacheckerpro.com/pricing/'; 28var $login_url = params['loginUrl'] || '/wp-login.php'; 29var $register_url = params['registerUrl'] || ''; 30var $guest_cap = parseInt(params['guestCap'] || 0, 10) || 0; 31var $guest_used = parseInt(params['guestUsed'] || 0, 10) || 0; 32var $guest_remaining = parseInt(params['guestRemaining'] || 0, 10) || 0; 33/* Daily cap for logged-in accounts without premium. 0 = admin set no cap. */ 34var $free_daily_cap = parseInt(params['freeDailyCap'] || 0, 10) || 0; 35var $free_daily_used = parseInt(params['freeDailyUsed'] || 0, 10) || 0; 36var $free_daily_remaining = parseInt(params['freeDailyRemaining'] || 0, 10) || 0; 37var $uses_custom_url_billing = !!params['usesCustomUrlBilling']; 38var $billing_unit = parseInt(params['billingUnit'] || 0, 10) || 700; 39var $requires_turnstile = !!params['requiresTurnstile'] && !$is_premium; 40var $turnstile_sitekey = params['turnstileSiteKey'] || ''; 41 42var dpcTurnstileToken = ''; 43var dpcTurnstileTicket = ''; 44var dpcTurnstileWidgetId = null; 45var dpcPendingDomains = null; 46var dpcTurnstileBusy = false; 47 48function dpcTurnstileRequired() { 49 return $requires_turnstile && !!$turnstile_sitekey; 50} 51 52function dpcTurnstileWrap() { 53 return document.getElementById('dapa-turnstile-wrap'); 54} 55 56function dpcShowTurnstile() { 57 var wrap = dpcTurnstileWrap(); 58 if (wrap) { 59 wrap.classList.add('is-visible'); 60 } 61} 62 63function dpcHideTurnstile() { 64 var wrap = dpcTurnstileWrap(); 65 if (wrap) { 66 wrap.classList.remove('is-visible'); 67 } 68} 69 70function dpcTurnstileMessage(text) { 71 var el = document.getElementById('dapa-turnstile'); 72 if (!el) { 73 return; 74 } 75 el.innerHTML = text 76 ? '<div class="dapa-turnstile-msg">' + text + '</div>' 77 : ''; 78} 79 80window.dpcOnTurnstileSuccess = function (token) { 81 dpcTurnstileToken = token || ''; 82 if (dpcPendingDomains && dpcTurnstileToken) { 83 var domains = dpcPendingDomains; 84 dpcPendingDomains = null; 85 startChecking(domains); 86 } 87}; 88window.dpcOnTurnstileExpired = function () { 89 dpcTurnstileToken = ''; 90}; 91window.dpcOnTurnstileError = function () { 92 dpcTurnstileToken = ''; 93}; 94 95function dpcRenderTurnstile() { 96 if (!dpcTurnstileRequired()) { 97 return; 98 } 99 var el = document.getElementById('dapa-turnstile'); 100 if (!el) { 101 return; 102 } 103 if (dpcTurnstileWidgetId !== null) { 104 return; 105 } 106 if (typeof turnstile === 'undefined' || typeof turnstile.render !== 'function') { 107 dpcTurnstileMessage('Captcha failed to load. Please refresh and try again.'); 108 return; 109 } 110 dpcTurnstileMessage(''); 111 dpcTurnstileWidgetId = turnstile.render(el, { 112 sitekey: $turnstile_sitekey, 113 action: 'dapa-check', 114 appearance: 'always', 115 size: 'normal', 116 theme: 'light', 117 callback: window.dpcOnTurnstileSuccess, 118 'expired-callback': window.dpcOnTurnstileExpired, 119 'error-callback': function () { 120 dpcTurnstileToken = '';
121 dpcTurnstileMessage('Captcha could not display. In Cloudflare use Managed widget, and add this site domain.'); 122 } 123 }); 124} 125 126function dpcLoadTurnstileApi(done) { 127 if (typeof turnstile !== 'undefined' && typeof turnstile.render === 'function') { 128 done(); 129 return; 130 } 131 var existing = document.getElementById('cf-turnstile-api'); 132 if (!existing) { 133 existing = document.createElement('script'); 134 existing.id = 'cf-turnstile-api'; 135 existing.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; 136 existing.async = true; 137 existing.defer = true; 138 existing.onerror = function () { 139 dpcTurnstileBusy = false; 140 dpcTurnstileMessage('Captcha script blocked. Disable adblock and try again.'); 141 }; 142 document.head.appendChild(existing); 143 } 144 var tries = 0; 145 var timer = setInterval(function () { 146 tries += 1; 147 if (typeof turnstile !== 'undefined' && typeof turnstile.render === 'function') { 148 clearInterval(timer); 149 dpcTurnstileBusy = false; 150 done(); 151 return; 152 } 153 if (tries > 50) { 154 clearInterval(timer); 155 dpcTurnstileBusy = false; 156 dpcTurnstileMessage('Captcha failed to load. Please refresh and try again.'); 157 } 158 }, 100); 159} 160 161function dpcRequestTurnstile() { 162 if (dpcTurnstileWidgetId !== null) { 163 return; 164 } 165 if (dpcTurnstileBusy) { 166 return; 167 } 168 dpcTurnstileBusy = true; 169 dpcLoadTurnstileApi(function () { 170 dpcTurnstileBusy = false; 171 var run = function () { 172 window.setTimeout(function () { 173 dpcRenderTurnstile(); 174 }, 50); 175 }; 176 if (typeof turnstile.ready === 'function') { 177 turnstile.ready(run); 178 } else { 179 run(); 180 } 181 }); 182} 183 184function dpcResetTurnstile() { 185 dpcTurnstileToken = ''; 186 dpcTurnstileTicket = ''; 187 dpcPendingDomains = null; 188 if (typeof turnstile !== 'undefined') { 189 try { 190 if (dpcTurnstileWidgetId !== null && typeof turnstile.remove === 'function') { 191 turnstile.remove(dpcTurnstileWidgetId); 192 } else if (dpcTurnstileWidgetId !== null && typeof turnstile.reset === 'function') { 193 turnstile.reset(dpcTurnstileWidgetId); 194 } 195 } catch (ignore) {} 196 } 197 dpcTurnstileWidgetId = null; 198 var el = document.getElementById('dapa-turnstile'); 199 if (el) { 200 el.innerHTML = ''; 201 } 202 dpcHideTurnstile(); 203} 204 205function dpcShowCaptchaRequired(doReset) { 206 if (doReset !== false) { 207 dpcResetTurnstile(); 208 } 209 if (typeof Swal !== 'undefined' && Swal.fire) { 210 Swal.fire({ 211 title: 'Please complete the captcha', 212 text: 'Solve the captcha above the Check button, then try again.', 213 icon: 'info' 214 }); 215 } else if (typeof swal !== 'undefined' && swal.fire) { 216 swal.fire('Please complete the captcha.'); 217 } 218} 219 220function dpcEnsureTurnstileThenStart(uniqueDomains) { 221 if (!dpcTurnstileRequired()) { 222 startChecking(uniqueDomains); 223 return; 224 } 225 if (dpcTurnstileToken) { 226 startChecking(uniqueDomains); 227 return; 228 } 229 dpcPendingDomains = uniqueDomains; 230 dpcShowTurnstile(); 231 dpcRequestTurnstile(); 232} 233 234/** 235 * Period-request units this Check will consume. 236 * Custom URL-cap premium: ceil(urls / 700); otherwise 1. 237 */ 238function dpcRequestsNeeded(urlCount) { 239 urlCount = parseInt(urlCount, 10) || 0; 240 if (urlCount < 1) return 0; 241 if ($uses_custom_url_billing && $billing_unit > 0) { 242 return Math.max(1, Math.ceil(urlCount / $billing_unit)); 243 } 244 return 1; 245} 246 247/* Virtual-scroll + sort/filter/export state. 248 * 249 * `dpcAllRowsData` is the canonical row store; HTML strings are derived on 250 * demand. This lets us drop them into Clusterize.js for rendering, while 251 * still being able to sort/filter/export without scanning the (potentially 252 * truncated) live DOM. */ 253var dpcAllRowsData = []; 254var dpcClusterize = null; 255 256var submit_button = document.getElementById('urls-submit'); 257 258var focusInput = document.getElementById('urls-input'); 259 260submit_button.addEventListener('click', handleInputURLs); 261 262focusInput.addEventListener("input", countURLs); 263 264/* ------------------------------------------------------------------ 265 * Free-tier upgrade popup (shown on hover of blurred cells) 266 * ----------------------------------------------------------------- */ 267(function () { 268 var popup = document.getElementById('dpc-upgrade-popup'); 269 if (!popup) return; 270 popup.innerHTML = '' 271 + '<div class="dpc-upgrade-popup__title">Premium feature</div>' 272 + '<div class="dpc-upgrade-popup__body">The free plan hides some results. Unlock every DA, PA, Spam Score, Linking Domains, Inbound Links, IP and Domain Age with the Premium plan.</div>' 273 + '<a class="dpc-upgrade-popup__cta" href="' + $upgrade_url + '" target="_blank" rel="noopener">Upgrade to Premium</a>'; 274 275 var hideTimer = null; 276 function positionFor(el) { 277 var rect = el.getBoundingClientRect(); 278 var pw = popup.offsetWidth; 279 var ph = popup.offsetHeight; 280 var top = rect.bottom + 8; 281 var left = rect.left + (rect.width / 2) - (pw / 2); 282 if (left < 8) left = 8; 283 if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8; 284 if (top + ph >
284 window.innerHeight - 8) top = rect.top - ph - 8; 285 popup.style.top = top + 'px'; 286 popup.style.left = left + 'px'; 287 } 288 function show(el) { 289 if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; } 290 popup.classList.add('is-visible'); 291 popup.setAttribute('aria-hidden', 'false'); 292 positionFor(el); 293 } 294 function hideSoon() { 295 if (hideTimer) clearTimeout(hideTimer); 296 hideTimer = setTimeout(function () { 297 popup.classList.remove('is-visible'); 298 popup.setAttribute('aria-hidden', 'true'); 299 }, 180); 300 } 301 document.addEventListener('mouseover', function (ev) { 302 var t = ev.target; 303 if (t && t.classList && t.classList.contains('dpc-blur')) show(t); 304 else if (t === popup || (popup.contains && popup.contains(t))) { 305 if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; } 306 } 307 }); 308 document.addEventListener('mouseout', function (ev) { 309 var t = ev.target; 310 if (t && t.classList && t.classList.contains('dpc-blur')) hideSoon(); 311 else if (t === popup || (popup.contains && popup.contains(t))) hideSoon(); 312 }); 313 document.addEventListener('click', function (ev) { 314 var t = ev.target; 315 if (t && t.classList && t.classList.contains('dpc-blur')) { 316 window.open($upgrade_url, '_blank', 'noopener'); 317 } 318 }); 319 window.addEventListener('scroll', function () { 320 popup.classList.remove('is-visible'); 321 }, { passive: true }); 322})(); 323 324function dpcBlurredCell() { 325 return '<span class="dpc-blur" data-upgrade="1" title="Upgrade to Premium">âââ</span>'; 326} 327 328function dpcUpdateUsage(usage) { 329 if (!usage || typeof usage !== 'object') return; 330 if (typeof usage.request_used === 'number') $request_used = usage.request_used; 331 if (typeof usage.request_cap === 'number') $request_cap = usage.request_cap; 332 if (typeof usage.request_remaining === 'number') $request_remaining = usage.request_remaining; 333 if (typeof usage.is_premium === 'boolean') $is_premium = usage.is_premium; 334 if (typeof usage.guest_used === 'number') $guest_used = usage.guest_used; 335 if (typeof usage.guest_cap === 'number') $guest_cap = usage.guest_cap; 336 if (typeof usage.guest_remaining === 'number') $guest_remaining = usage.guest_remaining; 337 if (typeof usage.free_daily_cap === 'number') $free_daily_cap = usage.free_daily_cap; 338 if (typeof usage.free_daily_used === 'number') $free_daily_used = usage.free_daily_used; 339 if (typeof usage.free_daily_remaining === 'number') $free_daily_remaining = usage.free_daily_remaining; 340 if (typeof usage.chunk_size === 'number' && usage.chunk_size > 0) $chunk_size = usage.chunk_size; 341 /* Keeps an already-open tab on the current input cap when an admin changes it. */ 342 if (typeof usage.url_cap === 'number' && usage.url_cap > 0) $checker_limit = usage.url_cap; 343 if (typeof usage.uses_custom_url_billing === 'boolean') { 344 $uses_custom_url_billing = usage.uses_custom_url_billing; 345 } 346 if (typeof usage.billing_unit === 'number' && usage.billing_unit > 0) { 347 $billing_unit = usage.billing_unit; 348 } 349} 350 351/* Open the XooTiX "Login / Signup Popup". We NEVER fall back to 352 * wp-login.php so the site's hidden admin URL stays hidden. 353 * 354 * IMPORTANT â how XooTiX trigger classes actually work on most sites: 355 * - `.xoo-el-action-sc.xoo-el-login-tgr` is the visible PAGE Login button. 356 * Clicking it OPENS the popup (Login tab). 357 * - `.xoo-el-reg-tgr` (with `data-tab="register"`) is the Sign Up TAB 358 * RENDERED INSIDE THE POPUP. It only switches tabs while the popup 359 * is already open â clicking it before the popup opens does nothing. 360 * - Most themes (including dapacheckerpro.com) only render the Login 361 * page button. So the correct flow for register is: 362 * 1) Click the page-level Login trigger to OPEN the popup. 363 * 2) Wait for the popup container to appear. 364 * 3) Click the internal `.xoo-el-reg-tgr` tab to switch form. 365 * 366 * @param {'login'|'register'} which Which form to open. 367 * @return {boolean} True if we triggered the popup (or attempted to). 368 */ 369function dpcOpenXootixLogin(which) { 370 which = which === 'register' ? 'register' : 'login'; 371 372 function $$(sel) { return document.querySelectorAll(sel); } 373 function $first(sel) { return document.querySelector(sel); } 374 375 function fireClick(el) { 376 if (!el) return false;
377 try { 378 if (window.jQuery) { 379 jQuery(el).trigger('click'); 380 } else { 381 el.click(); 382 } 383 return true; 384 } catch (e) { return false; } 385 } 386 387 /* Find the page-level XooTiX trigger that OPENS the popup. We prefer 388 * `.xoo-el-action-sc` (the shortcode-rendered link/button) and only 389 * accept triggers that live OUTSIDE the popup container, so we don't 390 * accidentally click an internal tab. */ 391 function findPageTrigger(form) { 392 var nodes = $$('.xoo-el-action-sc, .xoo-el-tgr, .xoo-el-login-tgr, .xoo-el-reg-tgr'); 393 var fallback = null; 394 for (var i = 0; i < nodes.length; i++) { 395 var n = nodes[i]; 396 if (n.closest && n.closest('.xoo-el-container')) continue; 397 var cls = n.className || ''; 398 if (form === 'register') { 399 if (/(^|\s)(xoo-el-reg-tgr|xoo-el-register-tgr)(\s|$)/.test(cls)) return n; 400 } else { 401 if (/(^|\s)xoo-el-login-tgr(\s|$)/.test(cls)) return n; 402 } 403 if (!fallback) fallback = n; 404 } 405 return fallback; 406 } 407 408 function isPopupVisible() { 409 var c = $first('.xoo-el-container'); 410 if (!c) return false; 411 var cs = window.getComputedStyle(c); 412 if (!cs || cs.display === 'none' || cs.visibility === 'hidden') return false; 413 return c.offsetWidth > 0 || c.offsetHeight > 0; 414 } 415 416 function clickInternalRegTab() { 417 var tab = $first('.xoo-el-container .xoo-el-reg-tgr, .xoo-el-form-nav .xoo-el-reg-tgr, .xoo-el-reg-tgr[data-tab="register"]'); 418 if (!tab) tab = $first('.xoo-el-reg-tgr'); 419 return fireClick(tab); 420 } 421 422 function tryPublicApi() { 423 try { 424 if (window.xoo_el_data && window.xoo_el_data.popup && typeof window.xoo_el_data.popup.open === 'function') { 425 window.xoo_el_data.popup.open(which); 426 return true; 427 } 428 } catch (e) {} 429 return false; 430 } 431 432 /* Inject a hidden trigger with the right class so XooTiX's delegated 433 * handler picks it up. Used as last resort when no page trigger exists. */ 434 function injectAndClick(klass) { 435 try { 436 var a = document.createElement('a'); 437 a.className = klass + ' xoo-el-action-sc'; 438 a.setAttribute('href', '#'); 439 a.style.cssText = 'position:absolute;left:-9999px;top:-9999px;width:1px;height:1px;opacity:0;pointer-events:auto;'; 440 document.body.appendChild(a); 441 fireClick(a); 442 setTimeout(function () { if (a && a.parentNode) a.parentNode.removeChild(a); }, 1500); 443 return true; 444 } catch (e) { return false; } 445 } 446 447 function showUnavailableFallback() { 448 if (typeof Swal !== 'undefined' && Swal.fire) { 449 Swal.fire({ 450 title: 'Sign in unavailable', 451 text: 'The sign-in window could not be opened. Please refresh the page and try again.', 452 icon: 'warning', 453 customClass: { popup: 'dpc-premium-confirm' }, 454 confirmButtonText: 'OK', 455 }); 456 } 457 } 458 459 /* Switch the popup to the requested tab if needed. Retries a few times 460 * because the popup container may animate in. */ 461 function switchTabWhenReady(form, attempts) { 462 attempts = attempts || 0; 463 if (form !== 'register') return; 464 if (isPopupVisible()) { 465 clickInternalRegTab(); 466 return; 467 } 468 if (attempts >= 20) return; 469 setTimeout(function () { switchTabWhenReady(form, attempts + 1); }, 100); 470 } 471 472 if (tryPublicApi()) { 473 switchTabWhenReady(which); 474 return true; 475 } 476 477 var pageTrigger = findPageTrigger(which); 478 479 if (which === 'register') { 480 /* If a real `.xoo-el-reg-tgr` page trigger exists outside the popup, 481 * a single click opens the popup directly on the register tab. */ 482 if (pageTrigger && /(^|\s)(xoo-el-reg-tgr|xoo-el-register-tgr)(\s|$)/.test(pageTrigger.className || '')) { 483 fireClick(pageTrigger); 484 switchTabWhenReady('register'); 485 return true; 486 } 487 /* Otherwise open the popup via the Login trigger and then flip 488 * to the Sign Up tab inside the popup. */ 489 var loginTrigger = pageTrigger || findPageTrigger('login'); 490 if (loginTrigger) { 491 fireClick(loginTrigger); 492 switchTabWhenReady('register'); 493 return true; 494 } 495 /* No on-page trigger at all â inject one. */ 496 injectAndClick('xoo-el-login-tgr'); 497 switchTabWhenReady('register'); 498 setTimeout(function () { if (!isPopupVisible()) showUnavailableFallback(); }, 900); 499 return true; 500 } 501 502 /* Login branch. */ 503 if (pageTrigger) { 504 fireClick(pageTrigger); 505 return true; 506 } 507 injectAndClick('xoo-el-login-tgr'); 508 setTimeout(function () { if (!isPopupVisible()) showUnavailableFallback(); }, 900); 509 return true; 510} 511 512/* Guest hard-cap reached â force login via XooTiX popup only. */ 513function dpcShowLoginRequiredPopup(payload) { 514 var cap = (payload && payload.cap) ? payload.cap : $guest_cap; 515 var used = (payload && payload.used) ? payload.used : $guest_used; 516 517 var html = '' 518 + '<div class="dpc-confirm-stats">' 519 + '<div class="dpc-confirm-stat">' 520 + '<div class="dpc-confirm-stat__label">Free uses today</div>' 521 + '<div class="dpc-confirm-stat__value">' + used + ' / ' + cap + '</div>' 522 + '</div>' 523 + '<div class="dpc-confirm-stat">' 524 + '<div class="dpc-confirm-stat__label">Resets at</div>' 525 + '<div class="dpc-confirm-stat__value" style="font-size:14px;">Midnight</div>' 526 + '</div>' 527 + '</div>' 528 + '<p style="margin:14px 0 0;color:#4a5568;font-size:13px;line-height:1.55;">' 529 + 'You have used your <strong>' + cap + '</strong> free checks for today. ' 530 + 'Please <strong>sign in</strong> to keep checking for free â logged-in users have no daily limit.' 531 + '</p>'; 532 533 Swal.fire({ 534 title: 'Sign in to continue', 535 html: html, 536 icon: null, 537 showCancelButton: !!$register_url, 538 confirmButtonText: 'Sign in', 539 cancelButtonText: 'Create an account', 540 reverseButtons: true, 541 focusConfirm: true, 542 allowOutsideClick: false, 543 customClass: { popup: 'dpc-premium-confirm' }, 544 }).then(function (res) { 545 if (res && res.isConfirmed) { 546 dpcOpenXootixLogin('login'); 547 } else if (res && res.dismiss === Swal.DismissReason.cancel && $register_url) { 548 dpcOpenXootixLogin('register'); 549 } 550 }); 551} 552 553/* Premium pre-submit confirmation popup. 554 * 555 * Default premium: One Check click = 1 request against the monthly quota. 556 * Custom URL-cap (>700): requests = ceil(urls / billing unit). */ 557function dpcPremiumConfirmIfNeeded(domainCount, fullCap) { 558 return new Promise(function (resolve) { 559 if (!$is_premium) return resolve(true); 560 561 var needed = dpcRequestsNeeded(domainCount); 562 var remaining = $request_cap > 0 ? Math.max(0, $request_cap - $request_used) : Infinity; 563 var willExceed = remaining !== Infinity && remaining < needed; 564 565 /* Hard block when not enough remaining requests (server also enforces). */ 566 if (willExceed) { 567 var exceedHtml; 568 if ($uses_custom_url_billing) { 569 exceedHtml = 'This check needs <strong>' + needed + '</strong> request' 570 + (needed === 1 ? '' : 's') 571 + ' (<strong>' + $billing_unit + '</strong> URLs = 1 request), but you only have <strong>' 572 + remaining + '</strong> left.'; 573 } else { 574 exceedHtml = 'You have used <strong>' + $request_used + ' / ' + $request_cap 575 + '</strong> requests this period. This check cannot be sent.'; 576 } 577 Swal.fire({ 578 title: 'Not enough requests', 579 html: exceedHtml, 580 icon: 'warning', 581 customClass: { popup: 'dpc-premium-confirm' }, 582 confirmButtonText: 'OK', 583 }).then(function () { resolve(false); }); 584 return; 585 } 586 587 try { 588 if (window.sessionStorage && sessionStorage.getItem('dpc_premium_confirm_seen') === '1') { 589 return resolve(true); 590 } 591 } catch (e) {} 592 593 var capLabel = $request_cap > 0 ? $request_cap : 'â'; 594 var remLabel = remaining === Infinity ? 'â' : remaining; 595 var afterLabel = remaining === Infinity ? 'â' : Math.max(0, remaining - needed); 596 var title = $uses_custom_url_billing 597 ? ('Tip: ' + $billing_unit + ' URLs = 1 request') 598 : 'Tip: 1 run = 1 request'; 599 600 var body; 601 if ($uses_custom_url_billing) { 602 body = 'Checking <strong>' + domainCount + '</strong>
602 URL' + (domainCount === 1 ? '' : 's') 603 + ' counts as <strong>' + needed + '</strong> request' + (needed === 1 ? '' : 's') 604 + ' (<strong>' + $billing_unit + '</strong> URLs = 1 request) from your monthly quota.' 605 + ' Submit up to <strong>' + fullCap + '</strong> URLs per check.'; 606 } else { 607 body = 'Checking <strong>' + domainCount + '</strong> URL' + (domainCount === 1 ? '' : 's') 608 + ' counts as <strong>1 request</strong> from your monthly quota.' 609 + ' Submit up to <strong>' + fullCap + '</strong> URLs per check.'; 610 } 611 612 var html = '' 613 + '<div class="dpc-confirm-stats">' 614 + '<div class="dpc-confirm-stat">' 615 + '<div class="dpc-confirm-stat__label">Requests left</div>' 616 + '<div class="dpc-confirm-stat__value">' + remLabel + ' / ' + capLabel + '</div>' 617 + '</div>' 618 + '<div class="dpc-confirm-stat">' 619 + '<div class="dpc-confirm-stat__label">After this run</div>' 620 + '<div class="dpc-confirm-stat__value">' + afterLabel + '</div>' 621 + '</div>' 622 + '</div>' 623 + '<p style="margin:14px 0 0;color:#4a5568;font-size:13px;line-height:1.55;">' + body + '</p>'; 624 625 Swal.fire({ 626 title: title, 627 html: html, 628 icon: null, 629 showCancelButton: true, 630 confirmButtonText: 'Continue', 631 cancelButtonText: 'Cancel', 632 reverseButtons: true, 633 focusConfirm: true, 634 customClass: { popup: 'dpc-premium-confirm' }, 635 buttonsStyling: true, 636 }).then(function (res) { 637 if (res && res.isConfirmed) { 638 try { sessionStorage.setItem('dpc_premium_confirm_seen', '1'); } catch (e) {} 639 resolve(true); 640 } else { 641 resolve(false); 642 } 643 }); 644 }); 645} 646 647function dpcHandleLimitReached(payload, ctx) { 648 var reason = (payload && payload.reason) || ''; 649 var msg, title; 650 var partial = ctx && ctx.totalChunks > 1 && ctx.chunkIndex > 0 651 ? '<br><br><span style="color:#5b6b7d;font-size:12px;">Partial results are shown below â you can download them as CSV.</span>' 652 : ''; 653 654 if (reason === 'request_cap_exceeded') { 655 title = 'Monthly limit reached'; 656 var neededNote = (payload && payload.requests_needed) 657 ? ' This check needed <strong>' + payload.requests_needed + '</strong> request' 658 + (payload.requests_needed === 1 ? '' : 's') + '.' 659 : ''; 660 msg = 'You have used <strong>' + (payload.used || 0) + ' / ' + (payload.cap || 0) + '</strong> requests this period.' 661 + neededNote 662 + ' Please contact admin to reset your counter or request a higher cap.' + partial; 663 } else if (reason === 'free_daily_cap_exceeded') { 664 title = 'Daily limit reached'; 665 msg = 'You have used <strong>' + (payload.used || 0) + ' / ' + (payload.cap || 0) + '</strong> checks today. ' 666 + 'Your free allowance resets tomorrow.' 667 + '<br><br>Upgrade to Premium for a much higher limit and no daily reset wait.' + partial; 668 } else if (reason === 'url_cap_exceeded') { 669 title = 'Limit reached'; 670 msg = 'This request exceeds the allowed URL limit. ' 671 + 'Please refresh the page and try again.' + ($is_premium ? '' : '<br><br>Upgrade to Premium to check more URLs at once.') + partial; 672 } else { 673 title = 'Limit reached'; 674 msg = ((payload && payload.message) ? payload.message : 'Request limit reached. Please try again later.') + partial; 675 } 676 Swal.fire({ 677 title: title, 678 html: msg, 679 icon: 'warning', 680 customClass: { popup: 'dpc-premium-confirm' }, 681 confirmButtonText: $is_premium ? 'OK' : 'See plans', 682 }).then(function (res) { 683 if (res && res.isConfirmed && !$is_premium) { 684 window.open($upgrade_url, '_blank', 'noopener'); 685 } 686 }); 687} 688 689function countURLs(event) { 690 let textareaValue = event.target.value; 691 if (textareaValue.length != 0) { 692 let urls = textareaValue 693 let lines = urls.split('\n'); 694 const filteredArray = lines.filter((element) => { 695 return element !== ''; 696 }); 697 let filteredBucket = []; 698 filteredArray.forEach(function (ingredient) { 699 const tempIngredient = ingredient.split(' '); 700 const filteredIngredient = tempIngredient.filter((element) => { 701 return element !== ''; 702 }); 703 filteredBucket.push(...filteredIngredient); 704 }); 705 706 let filteredDomains = []; 707 filteredBucket.forEach(function (instance) { 708 if (isValidDomain(instance) || isValidUrl(instance)) { 709 filteredDomains.push(instance); 710 } 711 }); 712 713 if (filteredDomains.length > 0) { 714 filteredDomains = [...new Set(filteredDomains)]; 715 } 716 717 718 if (filteredDomains.length > $checker_limit) { 719 jQuery(".url_counter").css("color", "red"); 720 } else { 721 jQuery(".url_counter").css("color", "#000"); 722 } 723 724 jQuery(".url_counter").text(filteredDomains.length); 725 } 726} 727 728function chunkArray(array, chunkSize) { 729 const chunkedArray = []; 730 731 for (let i = 0; i < array.length; i += chunkSize) { 732 const chunk = array.slice(i, i + chunkSize).join(', '); 733 // const chunk = array.slice(i, i + chunkSize); 734 chunkedArray.push(chunk); 735 } 736 737 return chunkedArray; 738} 739 740 741var facebook_icon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 14.4 14.4"><path fill="#039be5" d="M7.2 1.5a5.7 5.7 0 1 0 0 11.4 5.7 5.7 0 1 0 0-11.4Z"/><path fill="#fff" d="M7.972 8.711h1.475l.232-1.498H7.972v-.819c0-.623.203-1.174.786-1.174h.936V3.912
741c-.164-.022-.512-.071-1.169-.071-1.372 0-2.176.725-2.176 2.375v.997h-1.41v1.498h1.41v4.119c.279.042.562.07.853.07.263 0 .519-.024.772-.058V8.711z"/></svg>`; 742var instagram_icon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 14.4 14.4"><path fill="#FF3D00" d="M12.96 10.17c-.12.63-.63 1.11-1.26 1.2-.99.15-2.64.33-4.5.33-1.83 0-3.48-.18-4.5-.33-.63-.09-1.14-.57-1.26-1.2-.12-.69-.24-1.71-.24-2.97s.12-2.28.24-2.97c.12-.63.63-1.11 1.26-1.2.99-.15 2.64-.33 4.5-.33 1.86 0 3.48.18 4.5.33.63.09 1.14.57 1.26 1.2.12.69.27 1.71.27 2.97-.03 1.26-.15 2.28-.27 2.97z"/><path fill="#FFF" d="M6 9.3V5.1l3.6 2.1z"/></svg>`; 743var linkedin_icon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 14.4 14.4"><path fill="#0288D1" d="M12.6 11.1a1.5 1.5 0 0 1-1.5 1.5H3.3a1.5 1.5 0 0 1-1.5-1.5V3.3a1.5 1.5 0 0 1 1.5-1.5h7.8a1.5 1.5 0 0 1 1.5 1.5v7.8z"/><path fill="#FFF" d="M3.6 5.7h1.5v5.1H3.6zm.745-.6h-.008c-.448 0-.737-.334-.737-.75 0-.426.298-.75.754-.75s.737.324.746.75c0 .416-.289.75-.755.75zm6.455 5.7H9.3V8.07c0-.659-.367-1.109-.958-1.109-.45 0-.694.304-.812.597-.043.105-.03.395-.03.542v2.7H6V5.7h1.5v.785c.216-.335.555-.785 1.421-.785 1.073 0 1.878.675 1.878 2.182L10.8 10.8z"/></svg>`; 744var twitter_icon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 14.4 14.4"><path fill="#212121" fill-rule="evenodd" d="M11.4 12.6H3a1.2 1.2 0 0 1-1.2-1.2V3A1.2 1.2 0 0 1 3 1.8h8.4A1.2 1.2 0 0 1 12.6 3v8.4a1.2 1.2 0 0 1-1.2 1.2z" clip-rule="evenodd"/><path fill="#fff" d="M10.277 10.2H8.346l-4.197-6H6.08l4.197 6zm-1.701-.509h.769L5.85 4.709h-.769l3.495 4.982z"/><path fill="#fff" d="m4.76 10.2 2.161-2.503-.283-.375L4.147 10.2Zm2.575-3.684.271.387L9.941 4.2h-.6Z"/></svg>`; 745var pinterest_icon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 14.4 14.4"><path fill="#E60023" d="M13.2 7.2a6 6 0 0 1-6 6 6 6 0 0 1-6-6 6 6 0 0 1 12 0z"/><path fill="#FFF" d="M7.333 3.425c-2.59 0-3.964 1.738-3.964 3.631 0 .88.468 1.976 1.218 2.325.113.053.174.03.2-.08l.167-.681c.015-.06.007-.112-.041-.172-.248-.301-.447-.854-.447-1.369 0-1.324 1.002-2.604 2.709-2.604 1.474 0 2.506 1.004 2.506 2.441 0 1.623-.82 2.747-1.886 2.747-.589 0-1.03-.487-.889-1.085.169-.713.497-1.483.497-1.998 0-.46-.247-.845-.759-.845-.602 0-1.085.623-1.085 1.457 0 .531.179.891.179.891s-.595 2.514-.704 2.982c-.121.517-.074 1.243-.022 1.717.135.053.271.106.411.15.245-.398.611-1.052.746-1.573l.374-1.426c.196.373.767.689 1.375.689 1.81 0 3.114-1.664 3.114-3.732 0-1.983-1.618-3.466-3.699-3.466z"/></svg>`; 746var youtube_icon = `<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 14.4 14.4"><path fill="#FF3D00" d="M12.96 10.17c-.12.63-.63 1.11-1.26 1.2-.99.15-2.64.33-4.5.33-1.83 0-3.48-.18-4.5-.33-.63-.09-1.14-.57-1.26-1.2-.12-.69-.24-1.71-.24-2.97s.12-2.28.24-2.97c.12-.63.63-1.11 1.26-1.2.99-.15 2.64-.33 4.5-.33 1.86 0 3.48.18 4.5.33.63.09 1.14.57 1.26 1.2.12.69.27 1.71.27 2.97-.03 1.26-.15 2.28-.27 2.97z"/><path fill="#FFF" d="M6 9.3V5.1l3.6 2.1z"/></svg>`; 747 748function formatNumberAuto(num) { 749 if (num >= 1_000_000) { 750 return (num / 1_000_000).toFixed(2) + 'M'; 751 } else if (num >= 1_000) { 752 return (num / 1_000).toFixed(2) + 'K'; 753 } else { 754 return num.toString(); 755 } 756} 757 758 759async function processStringsSequentially(strings) { 760 var totalChunks = strings.length; 761 /* Results already delivered in this run, so the free spam-score 762 * allowance is counted once per run rather than once per chunk. */ 763 var delivered = 0; 764 for (let i = 0; i < strings.length; i++) { 765 dpcUpdateProgressLabel(i, totalChunks); 766 const stringToProcess = strings[i]; 767 const status = await processChunkWithMainFallback(stringToProcess, { 768 offset: delivered, 769 onLimitReached: function ($parsed) { 770 dpcHandleLimitReached($parsed, { chunkIndex: i, totalChunks: totalChunks }); 771 } 772 }); 773 774 if (status === 'stop' || status === 'limit') { 775 return; 776 } 777 778 delivered += stringToProcess.split(',').filter(function (s) { 779 return s.trim() !== ''; 780 }).length; 781 } 782 dpcUpdateProgressLabel(totalChunks, totalChunks); 783} 784 785function dpcApplySuccessResponse($parsed) { 786 dpcUpdateUsage($parsed.usage); 787 var sorted = Object.entries($parsed.data).sort((a, b) => a[1].seq - b[1].seq); 788 var batch = []; 789 var chunkUrls = []; 790 for (var k = 0; k < sorted.length; k++) { 791 var key = sorted[k][0]; 792 var val = sorted[k][1]; 793 var rowD = dpcRowDataFromUpstream(key, val); 794 dpcAllRowsData.push(rowD); 795 batch.push(dpcRowDataToHtml(rowD)); 796 chunkUrls.push(key); 797 } 798 dpcAppendRowsToTable(batch);
799 dpcEnrichDomainAge(chunkUrls); 800} 801 802function dpcHandleChunkResponse($parsed, progressCtx) { 803 progressCtx = progressCtx || {}; 804 805 if ($parsed.hasOwnProperty('status') && $parsed.status === 'login_required') { 806 dpcShowLoginRequiredPopup($parsed); 807 return 'stop'; 808 } 809 810 if ($parsed.hasOwnProperty('status') && $parsed.status === 'captcha_required') { 811 dpcShowCaptchaRequired(); 812 return 'stop'; 813 } 814 815 if ($parsed.hasOwnProperty('status') && $parsed.status === 'limit_reached') { 816 if (typeof progressCtx.onLimitReached === 'function') { 817 progressCtx.onLimitReached($parsed); 818 } 819 return 'limit'; 820 } 821 822 if ($parsed.hasOwnProperty('status') && $parsed.status == "error") { 823 const msg = (typeof $parsed.message === 'string' && $parsed.message.indexOf('Too many') !== -1) 824 ? 'Too many requests. Please wait a few minutes.' 825 : 'Unable to complete this request. Please try again.'; 826 swal.fire(msg); 827 return 'stop'; 828 } 829 830 if ($parsed.hasOwnProperty('success') && $parsed.success === false) { 831 swal.fire('Unable to complete this request. Please try again.'); 832 return 'stop'; 833 } 834 835 if ($parsed.hasOwnProperty('success') && $parsed.success === true) { 836 dpcApplySuccessResponse($parsed); 837 return 'ok'; 838 } 839 840 swal.fire('Unable to complete this request. Please try again.'); 841 return 'stop'; 842} 843 844async function processChunkWithMainFallback(stringToProcess, progressCtx) { 845 progressCtx = progressCtx || {}; 846 const result = await makeAjaxRequest(stringToProcess, { offset: progressCtx.offset || 0 }); 847 let $parsed = result; 848 849 if ($parsed.hasOwnProperty('status') && $parsed.status === 'retry_split') { 850 if ($parsed.usage) { 851 dpcUpdateUsage($parsed.usage); 852 } 853 var maxChunk = parseInt($parsed.max_chunk || 500, 10) || 500; 854 var splitToken = (typeof $parsed.split_token === 'string') ? $parsed.split_token : ''; 855 var domainList = stringToProcess.split(',').map(function (s) { return s.trim(); }).filter(Boolean); 856 var subChunks = chunkArray(domainList, maxChunk); 857 var totalSub = subChunks.length; 858 859 for (var j = 0; j < subChunks.length; j++) { 860 if (totalSub > 1 && typeof progressCtx.onSubProgress === 'function') { 861 progressCtx.onSubProgress(j, totalSub); 862 } 863 var subResult = await makeAjaxRequest(subChunks[j], { splitToken: splitToken }); 864 var handled = dpcHandleChunkResponse(subResult, progressCtx); 865 if (handled !== 'ok') { 866 return handled; 867 } 868 } 869 return 'ok'; 870 } 871 872 return dpcHandleChunkResponse($parsed, progressCtx); 873} 874 875/* Normalise one upstream metric row into a plain JS object so that 876 * sorting, filtering, CSV/Excel export and Clusterize.js rendering all 877 * use the SAME source of truth (the live DOM may only hold the visible 878 * window of rows when Clusterize is active). */ 879function dpcRowDataFromUpstream(url, val) { 880 function pick(obj, key, fallback) { 881 if (obj && Object.prototype.hasOwnProperty.call(obj, key) && obj[key] != null) { 882 return obj[key]; 883 } 884 return fallback; 885 } 886 return { 887 url: url, 888 domain_authority: pick(val, 'domain_authority', null), 889 page_authority: pick(val, 'page_authority', null), 890 spam_score: pick(val, 'spam_score', null), 891 linking_domains: pick(val, 'plds_to_pld', pick(val, 'linking_domains', null)), 892 inbound_links: pick(val, 'external_urls_to_pld', pick(val, 'inbound_links', null)), 893 full_metrics: !!(val && val.full_metrics), 894 spam_locked: !!(val && val.spam_locked), 895 da_locked: !!(val && val.da_locked), 896 age_locked: !!(val && val.age_locked), 897 ip: pick(val, 'ip', null), 898 age: pick(val, 'age', null), 899 }; 900} 901 902/* HTML for ONE table row. Renders the blurred-upgrade-prompt cells for 903 * free users (PA/LD/IL always; DA/SS when upstream returned no value; plus 904 * the server-picked free allowance for SS, DA and Domain Age). */ 905function dpcRowDataToHtml(d) {
906 var $da = d.domain_authority == null ? 'N/A' : d.domain_authority; 907 var $pa = d.page_authority == null ? 'N/A' : d.page_authority; 908 var $ss; 909 if (d.spam_score == null) { 910 $ss = 'N/A'; 911 } else if (d.spam_score > 5) { 912 $ss = '<span style="color:red; font-weight: bold;">' + d.spam_score + '</span>'; 913 } else { 914 $ss = d.spam_score; 915 } 916 var $ld = d.linking_domains == null ? 'N/A' : formatNumberAuto(d.linking_domains); 917 var $il = d.inbound_links == null ? 'N/A' : formatNumberAuto(d.inbound_links); 918 /* IP and Domain Age are enriched asynchronously from the domain-age 919 * bridge (see dpcEnrichDomainAge). Until the response lands the cells 920 * show a small spinner so the user knows a value is on the way. Once 921 * enriched, dpcSyncClusterizeFromData keeps Clusterize's row cache in 922 * sync so virtual scroll never shows stale spinners. */ 923 /* null = still waiting for domain-age bridge; any string (incl. N/A) 924 * means enrichment finished for that cell. */ 925 var $ip = d.ip == null ? dpcAgePendingHtml() : d.ip; 926 var $age = d.age == null ? dpcAgePendingHtml() : d.age; 927 928 if (!$is_premium && !d.full_metrics) { 929 $pa = $ld = $il = dpcBlurredCell(); 930 if (d.domain_authority == null) { 931 $da = dpcBlurredCell(); 932 } 933 if (d.spam_score == null) { 934 $ss = dpcBlurredCell(); 935 } 936 } 937 938 /* Free tier hides a slice of the results: the spam score past the per-run 939 * allowance, plus a stable pseudo-random subset of DA and Domain Age. 940 * The server already stripped those values, so prompt instead of N/A. */ 941 if (!$is_premium) { 942 if (d.spam_locked) $ss = dpcBlurredCell(); 943 if (d.da_locked) $da = dpcBlurredCell(); 944 if (d.age_locked) $age = dpcBlurredCell(); 945 } 946 947 return '' 948 + '<tr>' 949 + '<td style="text-align: left !important;">' + d.url + '</td>' 950 + '<td><span class="domain-score">' + $da + '</span></td>' 951 + '<td><span class="page-score">' + $pa + '</span></td>' 952 + '<td><span class="spam-score">' + $ss + '</span></td>' 953 + '<td><span class="linking-domains">' + $ld + '</span></td>' 954 + '<td><span class="inbound-links">' + $il + '</span></td>' 955 + '<td><span class="d-ip">' + $ip + '</span></td>' 956 + '<td><span class="d-age">' + $age + '</span></td>' 957 + '</tr>'; 958} 959 960/* Append a batch of pre-rendered <tr> strings to the results table. 961 * Prefers Clusterize.js when the row total justifies virtual scrolling 962 * (>100 rows) so the page stays responsive with thousands of results. */ 963function dpcAppendRowsToTable(htmlRows) { 964 if (!htmlRows || !htmlRows.length) return; 965 966 var useCluster = (typeof Clusterize !== 'undefined') 967 && (dpcAllRowsData.length > 100 || dpcClusterize); 968 969 if (useCluster) { 970 if (!dpcClusterize) { 971 /* First time we cross the virtual-scroll threshold: hand the 972 * FULL row set to Clusterize so the rows already in the DOM 973 * are replaced with the windowed render. */ 974 var existing = []; 975 for (var i = 0; i < dpcAllRowsData.length - htmlRows.length; i++) { 976 existing.push(dpcRowDataToHtml(dpcAllRowsData[i])); 977 } 978 try { 979 dpcClusterize = new Clusterize({ 980 rows: existing.concat(htmlRows), 981 scrollId: 'dpc-scroll-area', 982 contentId: 'dpc-tbody', 983 no_data_text: '', 984 rows_in_block: 50, 985 blocks_in_cluster: 4, 986 }); 987 return; 988 } catch (e) { 989 dpcClusterize = null; 990 } 991 } else { 992 dpcClusterize.append(htmlRows); 993 return; 994 } 995 } 996 997 /* Fallback path: small result sets or Clusterize unavailable. */ 998 var $tbody = jQuery('#dapa-stats > tbody'); 999 $tbody.append(htmlRows.join('')); 1000} 1001 1002/* Clear table + JS row store, ready for a fresh run. */ 1003function dpcResetResults() { 1004 dpcAllRowsData = []; 1005 dpcAgeRunId++; /* invalidate any in-flight enrich responses */ 1006 if (dpcClusterSyncTimer) { 1007 clearTimeout(dpcClusterSyncTimer); 1008 dpcClusterSyncTimer = null; 1009 } 1010 if (dpcAgeFinalizeTimer) { 1011 clearTimeout(dpcAgeFinalizeTimer); 1012 dpcAgeFinalizeTimer = null; 1013 } 1014 if (dpcClusterize) { 1015 try { dpcClusterize.destroy(true); } catch (e) {} 1016 dpcClusterize = null; 1017 } 1018 jQuery('#dapa-stats > tbody').html(''); 1019} 1020 1021/* ------------------------------------------------------------------ 1022 * Domain Age / IP enrichment (bridge to the domain-age plugin's API). 1023 * 1024 * DA PA cells render instantly. IP + Domain Age cells start with a 1025 * spinner; this code fetches the values for each DA PA chunk in the 1026 * background and patches the rows in place. Sub-chunked to 100 so users 1027 * see rows fill progressively for large premium batches. 1028 * ----------------------------------------------------------------- */ 1029var dpcAgeRunId = 0; 1030var dpcAgeFinalizeTimer = null; 1031var DPC_AGE_SUB_CHUNK = 100; 1032var DPC_AGE_FETCH_MS = 120000; 1033 1034function dpcNormalizeHost(url) { 1035 var v = String(url || '').toLowerCase().trim(); 1036 v = v.replace(/^https?:\/\//, '').replace(/^www\./, ''); 1037 v = v.split('/')[0].split(':')[0].replace(/\.$/, ''); 1038 return v; 1039} 1040 1041function dpcAgePendingHtml() { 1042 return '<span class="dpc-age-pending" title="Checking..."><span class="dpc-spin" aria-hidden="true"></span></span>'; 1043} 1044 1045function dpcAjaxUrl() { 1046 if (params && typeof params.ajaxUrl === 'string' && params.ajaxUrl) { 1047 return params.ajaxUrl; 1048 } 1049 if (typeof window.ajaxurl === 'string' && window.ajaxurl) { 1050 return window.ajaxurl; 1051 } 1052 return '/wp-admin/admin-ajax.php'; 1053} 1054
1055function dpcEnrichDomainAge(urls) { 1056 if (!urls || !urls.length) return; 1057 var runId = dpcAgeRunId; 1058 for (var i = 0; i < urls.length; i += DPC_AGE_SUB_CHUNK) { 1059 dpcFetchDomainAgeChunk(urls.slice(i, i + DPC_AGE_SUB_CHUNK), runId); 1060 } 1061} 1062 1063function dpcFetchDomainAgeChunk(urls, runId) { 1064 var fd = new FormData(); 1065 fd.append('action', 'dapa_check_domain_age'); 1066 fd.append('nonce', (params && params.dapa_nonce) ? params.dapa_nonce : ''); 1067 for (var i = 0; i < urls.length; i++) { 1068 fd.append('domains[]', urls[i]); 1069 } 1070 1071 var controller = typeof AbortController !== 'undefined' ? new AbortController() : null; 1072 var timeoutId = null; 1073 if (controller) { 1074 timeoutId = setTimeout(function () { 1075 try { controller.abort(); } catch (e) {} 1076 }, DPC_AGE_FETCH_MS); 1077 } 1078 1079 var fetchOpts = { 1080 method: 'POST', 1081 credentials: 'same-origin', 1082 body: fd, 1083 }; 1084 if (controller) fetchOpts.signal = controller.signal; 1085 1086 fetch(dpcAjaxUrl(), fetchOpts).then(function (r) { 1087 return r.json().catch(function () { return null; }); 1088 }).then(function (resp) { 1089 if (runId !== dpcAgeRunId) return; /* user cleared / reran */ 1090 if (!resp || !resp.success || !Array.isArray(resp.data)) { 1091 for (var i = 0; i < urls.length; i++) { 1092 dpcApplyAgeRow(urls[i], null, null); 1093 } 1094 dpcFlushClusterizeSync(); 1095 return; 1096 } 1097 var map = {}; 1098 for (var j = 0; j < resp.data.length; j++) { 1099 var row = resp.data[j]; 1100 if (row && typeof row.url === 'string') { 1101 map[row.url] = row; 1102 } 1103 } 1104 for (var k = 0; k < urls.length; k++) { 1105 var u = urls[k]; 1106 var hit = map[u]; 1107 dpcApplyAgeRow(u, hit ? hit.ip : null, hit ? hit.age : null); 1108 } 1109 dpcFlushClusterizeSync(); 1110 }).catch(function () { 1111 if (runId !== dpcAgeRunId) return; 1112 for (var i = 0; i < urls.length; i++) { 1113 dpcApplyAgeRow(urls[i], null, null); 1114 } 1115 dpcFlushClusterizeSync(); 1116 }).finally(function () { 1117 if (timeoutId) clearTimeout(timeoutId); 1118 }); 1119} 1120 1121/* Update one row's IP / Age cell + sync the canonical data store so 1122 * sort / filter / CSV / XLSX export pick up the new values. 1123 * 1124 * Clusterize.js keeps its own copy of pre-rendered <tr> HTML strings. 1125 * Patching only the live DOM is not enough â on scroll it re-mounts the 1126 * stale cached HTML (spinners). dpcScheduleClusterizeSync() rebuilds 1127 * Clusterize's row cache from dpcAllRowsData after each enrichment. */ 1128var dpcClusterSyncTimer = null; 1129 1130function dpcScheduleClusterizeSync() { 1131 if (!dpcClusterize) return; 1132 if (dpcClusterSyncTimer) clearTimeout(dpcClusterSyncTimer); 1133 dpcClusterSyncTimer = setTimeout(function () { 1134 dpcClusterSyncTimer = null; 1135 dpcSyncClusterizeFromData(); 1136 }, 40); 1137} 1138 1139function dpcSyncClusterizeFromData() { 1140 if (!dpcClusterize || !dpcAllRowsData.length) return; 1141 try { 1142 dpcClusterize.update(dpcAllRowsData.map(dpcRowDataToHtml)); 1143 } catch (e) {} 1144} 1145 1146function dpcFlushClusterizeSync() { 1147 if (dpcClusterSyncTimer) { 1148 clearTimeout(dpcClusterSyncTimer); 1149 dpcClusterSyncTimer = null; 1150 } 1151 dpcSyncClusterizeFromData(); 1152} 1153 1154/* Any row still pending after all enrich requests had time to finish 1155 * (including 120s timeout per sub-chunk) is marked N/A so spinners 1156 * never stay forever. */ 1157function dpcFinalizeStaleAgePending() { 1158 var changed = false; 1159 for (var i = 0; i < dpcAllRowsData.length; i++) { 1160 if (dpcAllRowsData[i].ip == null) { 1161 dpcAllRowsData[i].ip = 'N/A'; 1162 changed = true; 1163 } 1164 if (dpcAllRowsData[i].age == null) { 1165 dpcAllRowsData[i].age = 'N/A'; 1166 changed = true; 1167 } 1168 } 1169 if (changed) dpcFlushClusterizeSync(); 1170} 1171 1172function dpcScheduleAgeFinalize() { 1173 if (dpcAgeFinalizeTimer) clearTimeout(dpcAgeFinalizeTimer); 1174 dpcAgeFinalizeTimer = setTimeout(function () { 1175 dpcAgeFinalizeTimer = null; 1176 dpcFinalizeStaleAgePending(); 1177 }, DPC_AGE_FETCH_MS + 5000); 1178} 1179 1180function dpcApplyAgeRow(url, ip, age) { 1181 var ipText = (ip != null && String(ip).trim() !== '') ? String(ip).trim() : 'N/A'; 1182 var ageText = (age != null && String(age).trim() !== '') ? String(age).trim() : 'N/A'; 1183 var host = dpcNormalizeHost(url); 1184 var found = false;
1185 /* Rows whose Domain Age is withheld from free users keep the upgrade 1186 * badge â enrichment fills their IP cell only. */ 1187 var ageLocked = {}; 1188 1189 /* Apply to every row for this URL and any other URL that resolves to 1190 * the same host (http vs https, with/without www, duplicate paste). */ 1191 for (var i = 0; i < dpcAllRowsData.length; i++) { 1192 var rowUrl = dpcAllRowsData[i].url; 1193 if (rowUrl === url || (host && dpcNormalizeHost(rowUrl) === host)) { 1194 dpcAllRowsData[i].ip = ipText; 1195 if (!$is_premium && dpcAllRowsData[i].age_locked) { 1196 ageLocked[rowUrl] = true; 1197 } else { 1198 dpcAllRowsData[i].age = ageText; 1199 } 1200 found = true; 1201 } 1202 } 1203 if (!found) return; 1204 1205 /* Patch every visible DOM row that matches URL or host. */ 1206 var rows = document.querySelectorAll('#dapa-stats > tbody > tr'); 1207 for (var j = 0; j < rows.length; j++) { 1208 var tds = rows[j].cells; 1209 if (!tds || tds.length < 8) continue; 1210 var cellUrl = (tds[0].textContent || '').trim(); 1211 var cellHost = dpcNormalizeHost(cellUrl); 1212 if (cellUrl !== url && (!host || cellHost !== host)) continue; 1213 var ipSpan = tds[6].querySelector('.d-ip'); 1214 var ageSpan = tds[7].querySelector('.d-age'); 1215 if (ipSpan) ipSpan.textContent = ipText; 1216 if (ageSpan && !ageLocked[cellUrl]) ageSpan.textContent = ageText; 1217 } 1218 1219 dpcScheduleClusterizeSync(); 1220} 1221 1222/* Button label while a check is in progress (no internal batch details). */ 1223function dpcUpdateProgressLabel(done, total) { 1224 var $txt = jQuery('#urls-submit > .text'); 1225 if (!$txt.length) return; 1226 $txt.text(done >= total ? 'Check DA PA' : 'Checking'); 1227} 1228 1229function makeAjaxRequest(stringToProcess, opts) { 1230 opts = opts || {}; 1231 return new Promise((resolve, reject) => { 1232 const url = params.port + "checkdapa.php"; 1233 1234 testFetch().then(function (key) { 1235 let $payload = JSON.parse(key); 1236 $payload['domains'] = stringToProcess; 1237 if (opts.splitToken) { 1238 $payload['split_token'] = opts.splitToken; 1239 } 1240 if (opts.offset > 0) { 1241 $payload['offset'] = opts.offset; 1242 } 1243 if (dpcTurnstileRequired()) { 1244 if (dpcTurnstileTicket) { 1245 $payload['turnstile_ticket'] = dpcTurnstileTicket; 1246 } else if (dpcTurnstileToken) { 1247 $payload['cf-turnstile-response'] = dpcTurnstileToken; 1248 } 1249 } 1250 return fetch(url, { 1251 method: 'POST', 1252 credentials: 'same-origin', 1253 body: JSON.stringify($payload), 1254 headers: { 1255 'Content-Type': 'application/json', 1256 'X-Requested-With': 'XMLHttpRequest', 1257 'X-WP-Nonce': (typeof params !== 'undefined' && params.dapa_nonce) ? params.dapa_nonce : '', 1258 }, 1259 }).then(async function (response) { 1260 let data; 1261 try { 1262 data = JSON.parse(await response.text()); 1263 } catch (e) { 1264 data = null; 1265 } 1266 if (data && typeof data.turnstile_ticket === 'string' && data.turnstile_ticket) { 1267 dpcTurnstileTicket = data.turnstile_ticket; 1268 } 1269 if (response.status === 401 && data && data.status === 'login_required') { 1270 return data; 1271 } 1272 if (data && data.status === 'captcha_required') { 1273 return data; 1274 } 1275 if (response.status === 429 && data && data.status === 'limit_reached') { 1276 return data; 1277 } 1278 if (response.status === 429 && data && data.status === 'error' && typeof data.message === 'string') { 1279 return { 1280 status: 'error', 1281 message: 'Too many requests. Please wait a few minutes.', 1282 }; 1283 } 1284 if (!response.ok || data === null) { 1285 throw new Error('request_failed'); 1286 } 1287 return data; 1288 }); 1289 }).then(function (data) { 1290 resolve(data); 1291 }).catch(function () { 1292 reject(new Error('request_failed')); 1293 }); 1294 }); 1295} 1296 1297function nullHandler(variable) { 1298 if (variable == null) { 1299 return '<span style="color:red;">
1299N/A</span>' 1300 } else { 1301 return variable 1302 } 1303} 1304 1305 1306 1307function sendAjaxRequestsRecursive(outerArray, index = 0) { 1308 if (index === outerArray.length) { 1309 jQuery("#urls-submit").prop("disabled", false); 1310 jQuery("#urls-input").prop("disabled", false); 1311 jQuery("#urls-input").css("opacity", 1); 1312 jQuery("#urls-submit > .text").text('Find Data'); 1313 // jQuery("#download-stats").prop("disabled", false); 1314 jQuery(".loader-line").css("visibility", "hidden"); 1315 jQuery("#download-stats").prop("disabled", false); 1316 jQuery("#tool-clear").prop("disabled", false); 1317 jQuery(".loader_wmf").css("visibility", "hidden"); 1318 return; 1319 } 1320 1321 testFetch().then(function (key) { 1322 let $payload = JSON.parse(key); 1323 let tempDomainsArray = outerArray[index]; 1324 let ajaxRequests = []; 1325 tempDomainsArray.forEach(function ($url) { 1326 $payload['domain'] = $url; 1327 1328 let singleAjaXRequest = jQuery.ajax({ 1329 url: params.port + "webmailfinder.php", 1330 type: 'POST', 1331 dataType: 'json', 1332 contentType: 'application/json', 1333 data: JSON.stringify($payload), 1334 beforeSend: function () { 1335 }, 1336 success: function (data) { 1337 let $parsedData = data; 1338 let $domain_authority = $page_authority = $spam_score = $business_name = $email_address = $phone_number = $social_profiles = $web_category = $technology = null; 1339 let $status = "failed"; 1340 if ($parsedData.hasOwnProperty('success')) { 1341 if ($parsedData.success == true) { 1342 $status = "success"; 1343 // for dapa 1344 if ($parsedData.dapa_error == false) { 1345 let $tempDapaData = $parsedData.dapa_data[0]; 1346 if ($tempDapaData.hasOwnProperty('domain_authority')) { 1347 $domain_authority = $tempDapaData.domain_authority; 1348 } 1349 if ($tempDapaData.hasOwnProperty('page_authority')) { 1350 $page_authority = $tempDapaData.page_authority; 1351 } 1352 if ($tempDapaData.hasOwnProperty('spam_score')) { 1353 $spam_score = $tempDapaData.spam_score; 1354 } 1355 } 1356 // for wmf 1357 if ($parsedData.webm_error == false) { 1358 let $tempWmfdata = $parsedData.webm_data; 1359 if ($tempWmfdata.hasOwnProperty('successful')) { 1360 if ($tempWmfdata.successful == true) { 1361 if ($tempWmfdata.hasOwnProperty('data')) { 1362 let $worigindata = $tempWmfdata.data; 1363 // business name 1364 if ($worigindata.hasOwnProperty('siteName')) { 1365 $business_name = $worigindata.siteName; 1366 if ($business_name.trim().length == 0) { 1367 $business_name = null; 1368 } 1369 } 1370 // emails 1371 if ($worigindata.hasOwnProperty('emails')) { 1372 let $email_address_temp = $worigindata.emails; 1373 if ($email_address_temp.length != 0) { 1374 let $emails = []; 1375 $email_address_temp.forEach(function ($email_data) { 1376 $emails.push($email_data.email); 1377 }); 1378 $email_address = $emails.join(', '); 1379 } 1380 } 1381 // phones 1382 if ($worigindata.hasOwnProperty("phones")) { 1383 let $phones_temp = $worigindata.phones; 1384 if ($phones_temp.length != 0) { 1385 $phone_number = $phones_temp.join(', ') 1386 } 1387 } 1388 1389 // category 1390 if ($worigindata.hasOwnProperty('category')) { 1391 $web_category = $worigindata.category; 1392 if ($web_category.trim().length == 0) { 1393 $web_category = null; 1394 } 1395 } 1396 1397 // technology 1398 if ($worigindata.hasOwnProperty("technology")) { 1399 let $technology_temp = $worigindata.technology; 1400 if ($technology_temp.length != 0) { 1401 $technology = $technology_temp.join(', '); 1402 } 1403 } 1404 1405 // social media HTML 1406 let $social_temp_data = ""; 1407 if ($worigindata.hasOwnProperty("social")) { 1408 let $socialMedia = $worigindata.social; 1409 let $facebook = $socialMedia.facebook; 1410 if ($facebook.trim().length != 0) { 1411 $social_temp_data += `<span><a target="_blank" href="${$facebook}">${facebook_icon}</a></span>`;
1412 } 1413 let $instagram = $socialMedia.instagram; 1414 if ($instagram.trim().length != 0) { 1415 $social_temp_data += `<span><a target="_blank" href="${$instagram}">${instagram_icon}</a></span>`; 1416 } 1417 let $linkedin = $socialMedia.linkedin; 1418 if ($linkedin.trim().length != 0) { 1419 $social_temp_data += `<span><a target="_blank" href="${$linkedin}">${linkedin_icon}</a></span>`; 1420 } 1421 let $pinterest = $socialMedia.pinterest; 1422 if ($pinterest.trim().length != 0) { 1423 $social_temp_data += `<span><a target="_blank" href="${$pinterest}">${pinterest_icon}</a></span>`; 1424 } 1425 let $twitter = $socialMedia.twitter; 1426 if ($twitter.trim().length != 0) { 1427 $social_temp_data += `<span><a target="_blank" href="${$twitter}">${twitter_icon}</a></span>`; 1428 } 1429 let $youtube = $socialMedia.youtube; 1430 if ($youtube.trim().length != 0) { 1431 $social_temp_data += `<span><a target="_blank" href="${$youtube}">${youtube_icon}</a></span>`; 1432 } 1433 } 1434 1435 if ($social_temp_data.trim().length != 0) { 1436 $social_profiles = '<div class="sm_profiles">' + $social_temp_data + '</div>'; 1437 } 1438 } 1439 } 1440 } 1441 } 1442 } else { 1443 1444 } 1445 } else { 1446 1447 } 1448 // console.log(JSON.stringify({"da":$domain_authority, "pa":$page_authority, "ss":$spam_score, "bn": $business_name, "em":$email_address, "ph":$phone_number, "cat":$web_category, "tech":$technology, "sm": $social_profiles, "status":$status})); 1449 let $before = jQuery('#dapa-stats > tbody').html(); 1450 1451 let $after = $before + `<tr> 1452 <td>${$url}</td> 1453 <td><span class="business_name">${nullHandler($business_name)}</span></td> 1454 <td><span class="domain-score">${nullHandler($domain_authority)}</span></td> 1455 <td><span class="page-score">${nullHandler($page_authority)}</span></td> 1456 <td><span class="spam-score">${nullHandler($spam_score)}</span></td> 1457 <td><span class="email_address">${nullHandler($email_address)}</span></td> 1458 <td><span class="phone_no">${nullHandler($phone_number)}</span></td> 1459 <td><span class="social_p">${nullHandler($social_profiles)}</span></td> 1460 <td><span class="web_cat">${nullHandler($web_category)}</span></td> 1461 <td><span class="technology">${nullHandler($technology)}</span></td> 1462 <td><span class="rstat">${nullHandler($status)}</span></td> 1463 </tr>`; 1464 jQuery('#dapa-stats > tbody').html($after); 1465 }, 1466 error: function (jqXHR, textStatus, errorThrown) { 1467 // console.log('Error: AJAX request failed.'); 1468 // console.log('Status:', textStatus); 1469 // console.log('Error Thrown:', errorThrown); 1470 let $domain_authority = $page_authority = $spam_score = $business_name = $email_address = $phone_number = $social_profiles = $web_category = $technology = null; 1471 let $status = "failed"; 1472 // console.log(JSON.stringify({"da":$domain_authority, "pa":$page_authority, "ss":$spam_score, "bn": $business_name, "em":$email_address, "ph":$phone_number, "cat":$web_category, "tech":$technology, "sm": $social_profiles, "status":$status})); 1473 let $before = jQuery('#dapa-stats > tbody').html(); 1474 1475 let $after = $before + `<tr> 1476 <td>${$url}</td> 1477 <td><span class="business_name">${nullHandler($business_name)}</span></td> 1478 <td><span class="domain-score">${nullHandler($domain_authority)}</span></td> 1479 <td><span class="page-score">${nullHandler($page_authority)}</span></td> 1480 <td><span class="spam-score">${nullHandler($spam_score)}</span></td> 1481 <td><span class="email_address">${nullHandler($email_address)}</span></td> 1482 <td><span class="phone_no">${nullHandler($phone_number)}</span></td> 1483 <td><span class="social_p">${nullHandler($social_profiles)}</span></td> 1484 <td><span class="web_cat">${nullHandler($web_category)}</span></td> 1485 <td><span class="technology">${nullHandler($technology)}</span></td> 1486 <td><span class="rstat">${nullHandler($status)}</span></td> 1487 </tr>`; 1488 jQuery('#dapa-stats > tbody').html($after); 1489 }, 1490 complete: function () { 1491 } 1492 }); 1493 ajaxRequests.push(singleAjaXRequest); 1494 }); 1495 jQuery.when.apply(jQuery, ajaxRequests).then(function () { 1496 sendAjaxRequestsRecursive(outerArray, index + 1); 1497 }); 1498 }); 1499} 1500 1501function isValidUrl(input) { 1502 const urlPattern = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w.-]*)*\/?$/; 1503 return urlPattern.test(input); 1504} 1505 1506function isValidDomain(input) { 1507 const domainPattern = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i; 1508 return domainPattern.test(input); 1509} 1510 1511function handleInputURLs(event) { 1512 let urls = document.getElementById('urls-input').value; 1513 if (urls.trim().length == 0) { 1514 swal.fire("Please enter urls in the textarea."); 1515 } else { 1516 let lines = urls.split('\n'); 1517 const filteredArray = lines.filter((element) => { 1518 return element !== ''; 1519 }); 1520 let filteredBucket = []; 1521 filteredArray.forEach(function (ingredient) { 1522 const tempIngredient = ingredient.split(' '); 1523 const filteredIngredient = tempIngredient.filter((element) => { 1524 return element !== ''; 1525 }); 1526 filteredBucket.push(...filteredIngredient); 1527 }); 1528 1529 let filteredDomains = []; 1530 filteredBucket.forEach(function (instance) { 1531 if (isValidDomain(instance) || isValidUrl(instance)) { 1532 filteredDomains.push(instance); 1533 } 1534 }); 1535 1536 if (filteredDomains.length == 0) { 1537 swal.fire("No valid domain found in the data provided."); 1538 return; 1539 } 1540 1541 if (!$is_logged_in && $guest_cap > 0 && $guest_used >= $guest_cap) { 1542 dpcShowLoginRequiredPopup({ 1543 used: $guest_used, 1544 cap: $guest_cap, 1545 login_url: $login_url, 1546 }); 1547 return; 1548 } 1549 1550 let uniqueDomains = [...new Set(filteredDomains)]; 1551 1552 if (uniqueDomains.length > $checker_limit) { 1553 if (!$is_premium) { 1554 Swal.fire({ 1555 title: 'Free plan limit', 1556 html: 'Free plan allows up to <strong>' + $checker_limit + '</strong> URLs per check. ' 1557 + 'You submitted ' + uniqueDomains.length + '. ' 1558 + '<br><br>Upgrade to Premium to check up to ' + $checker_limit + ' URLs at a time.', 1559 icon: 'info', 1560 customClass: { popup: 'dpc-premium-confirm' }, 1561 showCancelButton: true, 1562 confirmButtonText: 'See plans', 1563 cancelButtonText: 'Check first ' + $checker_limit + ' only', 1564 reverseButtons: true, 1565 }).then(function (res) { 1566 if (res && res.isConfirmed) { 1567 window.open($upgrade_url, '_blank', 'noopener'); 1568 } else { 1569 uniqueDomains = uniqueDomains.slice(0, $checker_limit); 1570 dpcEnsureTurnstileThenStart(uniqueDomains); 1571 } 1572 }); 1573 return; 1574 } 1575 swal.fire(`First ${$checker_limit} domains/urls will be checked.`); 1576 uniqueDomains = uniqueDomains.slice(0, $checker_limit); 1577 } 1578 1579 if (!$is_logged_in && $guest_cap > 0) { 1580 var remaining = Math.max(0, $guest_cap - $guest_used); 1581 if (remaining <= 3 && remaining > 0) { 1582 try { 1583 var seenKey = 'dpc_guest_low_warning_' + new Date().toISOString().slice(0, 10); 1584 if (!sessionStorage.getItem(seenKey)) { 1585 sessionStorage.setItem(seenKey, '1'); 1586 Swal.fire({ 1587 title: 'Almost out of free uses', 1588 html: 'Only <strong>' + remaining + '</strong> free check' + (remaining === 1 ? '' : 's') + ' left today. ' 1589 + '<br>' + ($free_daily_cap > 0 1590 ? 'Sign in for ' + $free_daily_cap + ' checks a day.' 1591 : 'Sign in for unlimited free checks.'), 1592 icon: 'info', 1593 customClass: { popup: 'dpc-premium-confirm' }, 1594 showCancelButton: true, 1595 confirmButtonText: 'Sign in', 1596 cancelButtonText: 'Continue', 1597 reverseButtons: true, 1598 }).then(function (res) { 1599 if (res && res.isConfirmed) { 1600 dpcOpenXootixLogin('login'); 1601 return; 1602 } 1603 dpcPremiumConfirmIfNeeded(uniqueDomains.length, $checker_limit).then(function (proceed) { 1604 if (!proceed) return; 1605 dpcEnsureTurnstileThenStart(uniqueDomains); 1606 }); 1607 }); 1608 return; 1609 } 1610 } catch (e) {} 1611 } 1612 } 1613 1614 /* Same courtesy warning for logged-in free accounts, when the admin 1615 * has set a daily cap. Upgrade is the way out here, not signing in. */ 1616 if ($is_logged_in && !$is_premium && $free_daily_cap > 0) { 1617 var freeLeft = Math.max(0, $free_daily_cap - $free_daily_used); 1618 if (freeLeft <= 3 && freeLeft > 0) { 1619 try { 1620 var freeSeenKey = 'dpc_free_low_warning_' + new Date().toISOString().slice(0, 10); 1621 if (!sessionStorage.getItem(freeSeenKey)) { 1622 sessionStorage.setItem(freeSeenKey, '1'); 1623 Swal.fire({ 1624 title: 'Almost out of checks today', 1625 html: 'Only <strong>' + freeLeft + '</strong> check' + (freeLeft === 1 ? '' : 's') + ' left today. ' 1626 + '<br>Your allowance resets tomorrow, or upgrade for a much higher limit.', 1627 icon: 'info', 1628 customClass: { popup: 'dpc-premium-confirm' }, 1629 showCancelButton: true, 1630 confirmButtonText: 'See plans', 1631 cancelButtonText: 'Continue', 1632 reverseButtons: true, 1633 }).then(function (res) { 1634 if (res && res.isConfirmed) { 1635 window.open($upgrade_url, '_blank', 'noopener'); 1636 return; 1637 } 1638 dpcPremiumConfirmIfNeeded(uniqueDomains.length, $checker_limit).then(function (proceed) { 1639 if (!proceed) return; 1640 dpcEnsureTurnstileThenStart(uniqueDomains); 1641 }); 1642 }); 1643 return; 1644 } 1645 } catch (e) {} 1646 } 1647 } 1648 1649 dpcPremiumConfirmIfNeeded(uniqueDomains.length, $checker_limit).then(function (proceed) { 1650 if (!proceed) return; 1651 dpcEnsureTurnstileThenStart(uniqueDomains); 1652 }); 1653 } 1654} 1655 1656function startChecking(uniqueDomains) { 1657 dpcResetResults(); 1658 jQuery("#urls-submit").prop("disabled", true); 1659 jQuery("#urls-input").prop("disabled", true); 1660 jQuery("#urls-input").css("opacity", 0.5); 1661 jQuery("#urls-submit > .text").text('Checking'); 1662 jQuery(".output_conatiner").css('display', 'block'); 1663 jQuery(".filters-parent").css('display', 'flex'); 1664 jQuery(".loader-line").css("visibility", "visible"); 1665 jQuery("#download-stats").prop("disabled", true); 1666 jQuery("#tool-clear").prop("disabled", true); 1667 jQuery(".loader_wmf").css("visibility", "visible"); 1668 1669 /* Premium: send the full URL list in one run; server retry_split handles 1670 * chunk-sized upstream batches (500 + remainder). Default premium bills 1 1671 * request; custom URL-cap (>700) bills ceil(urls / billingUnit). 1672 * Free: chunk at 10 per AJAX request. */ 1673 var perChunk = $chunk_size > 0 ? $chunk_size : ($is_premium ? 500 : 10); 1674 var bucketChunks = $is_premium 1675 ? [uniqueDomains.join(', ')] 1676 : chunkArray(uniqueDomains, perChunk); 1677 dpcUpdateProgressLabel(0, bucketChunks.length); 1678 1679 processStringsSequentially(bucketChunks) 1680 .then(() => { 1681 jQuery("#urls-submit").prop("disabled", false); 1682 jQuery("#urls-input").prop("disabled", false); 1683 jQuery("#urls-input").css("opacity", 1); 1684 jQuery("#urls-submit > .text").text('Check DA PA'); 1685 // jQuery("#download-stats").prop("disabled", false); 1686 jQuery(".loader-line").css("visibility", "hidden"); 1687 jQuery("#download-stats").prop("disabled", false); 1688 jQuery("#tool-clear").prop("disabled", false); 1689 jQuery(".loader_wmf").css("visibility", "hidden"); 1690 dpcResetTurnstile(); 1691 dpcScheduleAgeFinalize(); 1692 }) 1693 .catch(() => { 1694 dpcResetTurnstile(); 1695 Swal.fire({ 1696 text: 'Something went wrong. Please refresh the page and try again.', 1697 icon: 'error', 1698 confirmButtonText: 'OK' 1699 }).then(() => { 1700 location.reload(true); 1701 }); 1702 }); 1703} 1704 1705var clear_button = document.getElementById("tool-clear"); 1706clear_button.addEventListener("click", clearInstalls); 1707 1708function clearInstalls() { 1709 jQuery("#urls-input").val(''); 1710 jQuery(".output_conatiner").css("display", "none"); 1711 jQuery(".filters-parent").css('display', 'none'); 1712 dpcResetResults(); 1713 jQuery("#download-stats").prop("disabled", true); 1714 jQuery("#tool-clear").prop("disabled", true); 1715 jQuery(".url_counter").text("0"); 1716 jQuery('html, body').animate({ 1717 scrollTop: jQuery(".dapa_tool_container").offset().top 1718 }, 1000); 1719} 1720 1721 1722// ================ filter code ====================== 1723 1724// References to filter controls 1725// const toggleFiltersBtn = document.getElementById("toggle-filters");
1726const filterControls = document.getElementById("filter-controls"); 1727const daRange = document.getElementById("da-range"); 1728const daNumber = document.getElementById("da-number"); 1729const paRange = document.getElementById("pa-range"); 1730const paNumber = document.getElementById("pa-number"); 1731const ssRange = document.getElementById("ss-range"); 1732const ssNumber = document.getElementById("ss-number"); 1733 1734const daValue = document.getElementById("da-value"); 1735const paValue = document.getElementById("pa-value"); 1736const ssValue = document.getElementById("ss-value"); 1737const toggleFiltersDiv = document.getElementById("toggle-filters"); 1738 1739let filtersEnabled = false; 1740 1741// Toggle filters enable/disable 1742toggleFiltersDiv.addEventListener("click", () => { 1743 if ($checker_limit < 100) { 1744 swal.fire("Upgrade your plan!"); 1745 return; 1746 } 1747 filtersEnabled = !filtersEnabled; 1748 1749 // Update the enable/disable status of filter inputs 1750 const status = !filtersEnabled; 1751 [daRange, daNumber, paRange, paNumber, ssRange, ssNumber].forEach((el) => { 1752 el.disabled = status; 1753 }); 1754 1755 // Update div state and text 1756 toggleFiltersDiv.textContent = filtersEnabled ? "Disable Filters" : "Enable Filters"; 1757 toggleFiltersDiv.classList.toggle("enabled", filtersEnabled); 1758 1759 // Show/hide filter values 1760 const displayStyle = filtersEnabled ? "inline" : "none"; 1761 [daValue, paValue, ssValue].forEach((el) => { 1762 el.style.display = displayStyle; 1763 }); 1764 1765 // Reset filters if disabled 1766 if (!filtersEnabled) { 1767 resetFilters(); 1768 } 1769}); 1770 1771 1772/* Reset filters: re-render the FULL data set. We rebuild from 1773 * `dpcAllRowsData` (not DOM) because Clusterize.js only ever keeps a small 1774 * window of rows mounted, so DOM-based "show all" wouldn't bring back 1775 * rows that were filtered out earlier. */ 1776function resetFilters() { 1777 dpcRenderRows(dpcAllRowsData); 1778} 1779 1780/* Re-render the results table from a (possibly filtered/sorted) array of 1781 * row data objects. Uses Clusterize.js when appropriate. */ 1782function dpcRenderRows(rows) { 1783 var html = (rows || []).map(dpcRowDataToHtml); 1784 if (dpcClusterize) { 1785 try { dpcClusterize.update(html); return; } catch (e) {} 1786 } 1787 if (typeof Clusterize !== 'undefined' && html.length > 100) { 1788 try { if (dpcClusterize) dpcClusterize.destroy(true); } catch (e) {} 1789 try { 1790 dpcClusterize = new Clusterize({ 1791 rows: html, 1792 scrollId: 'dpc-scroll-area', 1793 contentId: 'dpc-tbody', 1794 no_data_text: '', 1795 rows_in_block: 50, 1796 blocks_in_cluster: 4, 1797 }); 1798 return; 1799 } catch (e) { dpcClusterize = null; } 1800 } 1801 jQuery('#dapa-stats > tbody').html(html.join('')); 1802} 1803 1804/* Sort `dpcAllRowsData` in place by a numeric column key and re-render. 1805 * Called by the inline header-click handler in dapa.php. */ 1806window.dpcSortByColumn = function (columnIndex, ascending) { 1807 if (!Array.isArray(dpcAllRowsData) || dpcAllRowsData.length === 0) return; 1808 var keyMap = { 1809 1: 'domain_authority', 1810 2: 'page_authority', 1811 3: 'spam_score', 1812 4: 'linking_domains', 1813 5: 'inbound_links', 1814 }; 1815 var key = keyMap[columnIndex]; 1816 if (!key) return; 1817 dpcAllRowsData.sort(function (a, b) { 1818 var va = parseFloat(a[key]); if (isNaN(va)) va = 0; 1819 var vb = parseFloat(b[key]); if (isNaN(vb)) vb = 0; 1820 return ascending ? (va - vb) : (vb - va); 1821 }); 1822 dpcRenderRows(dpcAllRowsData); 1823}; 1824 1825/* Hand the FULL row HTML to a callback (used by CSV / XLSX exporters that 1826 * walk the DOM). With Clusterize active the DOM only holds the visible 1827 * window; we materialise everything for the duration of `cb`, then snap 1828 * back to the windowed render. */ 1829window.dpcWithFullRowsRendered = function (cb) { 1830 var hadCluster = !!dpcClusterize; 1831 var allHtml = (dpcAllRowsData || []).map(dpcRowDataToHtml); 1832 if (hadCluster) { 1833 try { dpcClusterize.destroy(true); } catch (e) {} 1834 dpcClusterize = null; 1835 } 1836 if (allHtml.length) { 1837 jQuery('#dapa-stats > tbody').html(allHtml.join('')); 1838 } 1839 try { cb && cb(); } 1840 finally { 1841 if (hadCluster && typeof Clusterize !== 'undefined' && allHtml.length > 100) { 1842 try { 1843 dpcClusterize = new Clusterize({ 1844 rows: allHtml, 1845 scrollId: 'dpc-scroll-area', 1846 contentId: 'dpc-tbody', 1847 no_data_text: '', 1848 rows_in_block: 50, 1849 blocks_in_cluster: 4, 1850 }); 1851 } catch (e) {} 1852 } 1853 } 1854}; 1855 1856// Synchronize range and number inputs 1857function syncInputs(range, number, valueDisplay) { 1858 range.addEventListener("input", () => { 1859 number.value = range.value; 1860 valueDisplay.textContent = range.value;
1861 if (filtersEnabled) filterTable(); 1862 }); 1863 1864 number.addEventListener("input", () => { 1865 range.value = number.value; 1866 valueDisplay.textContent = number.value; 1867 if (filtersEnabled) filterTable(); 1868 }); 1869} 1870 1871syncInputs(daRange, daNumber, daValue); 1872syncInputs(paRange, paNumber, paValue); 1873syncInputs(ssRange, ssNumber, ssValue); 1874 1875/* Filter against the data store (not the DOM) so it works correctly with 1876 * Clusterize.js, which only keeps the visible window mounted. */ 1877function filterTable() { 1878 if ($checker_limit < 100) { 1879 swal.fire("Upgrade your plan!"); 1880 return; 1881 } 1882 var daThreshold = parseInt(daRange.value, 10); 1883 var paThreshold = parseInt(paRange.value, 10); 1884 var ssThreshold = parseInt(ssRange.value, 10); 1885 1886 var matched = (dpcAllRowsData || []).filter(function (r) { 1887 var da = parseInt(r.domain_authority, 10); if (isNaN(da)) da = 0; 1888 var pa = parseInt(r.page_authority, 10); if (isNaN(pa)) pa = 0; 1889 var ss = parseInt(r.spam_score, 10); if (isNaN(ss)) ss = 0; 1890 return da >= daThreshold && pa >= paThreshold && ss <= ssThreshold; 1891 }); 1892 dpcRenderRows(matched); 1893}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.