From 04aaba9f65e34ec0712f4e15755a52d0ed4e2ede Mon Sep 17 00:00:00 2001 From: svedge Date: Wed, 23 Sep 2026 09:35:48 +0500 Subject: [PATCH 01/21] Upd. Scan. Improve UX. --- css/spbc-admin.min.css | 2 +- css/src/spbc-admin.css | 4 + inc/spbc-scanner.php | 19 +++ inc/spbc-settings.php | 3 + js/public/spbct-react-bundle.js | 4 +- js/spbc-scanner-plugin.min.js | 2 +- .../TabScanner/ScannerInfo/ScanButton.js | 2 +- .../TabContent/TabScanner/TabScanner.js | 32 +++- js/src/spbc-scanner-plugin.js | 1 + lib/CleantalkSP/Common/Transaction.php | 9 + lib/CleantalkSP/SpbctWP/Scanner/Helper.php | 3 + .../SpbctWP/Scanner/ScannerQueue.php | 154 ++++++++++++++++++ .../Scanner/ScanningLog/ScanningLogFacade.php | 8 + .../SpbctWP/Scanner/ScanningLog/Template.php | 20 +-- lib/CleantalkSP/SpbctWP/SpbcEnqueue.php | 2 +- .../classSurface/WideTreeCacheReproTest.php | 128 +++++++++++++++ 16 files changed, 373 insertions(+), 20 deletions(-) create mode 100644 tests/lib/CleantalkSP/SpbctWP/Scanner/classSurface/WideTreeCacheReproTest.php diff --git a/css/spbc-admin.min.css b/css/spbc-admin.min.css index 8e0999d87..8766ed620 100644 --- a/css/spbc-admin.min.css +++ b/css/spbc-admin.min.css @@ -1 +1 @@ -.spbc_meta_links{}.spbc_translate_links{color:rgba(150, 150, 20, 1)}.spbc_faq_links{color:rgba(20, 150, 20, 1)}.spbc_support_links{color:rgba(150, 20, 20, 1)}.spbc_setting_links{color:rgba(20, 20, 150, 1)}.spbc_translate_links:hover{color:rgba(210, 210, 20, 1) !important}.spbc_faq_links:hover{color:rgba(20, 250, 20, 1) !important}.spbc_support_links:hover{color:rgba(250, 20, 20, 1) !important}.spbc_setting_links:hover{color:rgba(20, 20, 250, 1) !important}.spbc_hide{display:none}.spbc_hide_table_cell_desc{display:none;max-width:90%}.--hide{display:none}.spbcRed{color:red}.ctlk---red{color:red}.spbcGreen{color:green}.spbcBlue{color:blue}.spbcGray{color:gray}.spbcTextCenter{text-align:center}.spbc-overflow--visible{overflow:visible !important}.-display--inline-block{display:inline-block}._animate--rotate{-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}#wp-admin-bar-cleantalk_admin_bar__parent_node{margin-right:5px}#wp-admin-bar-cleantalk_admin_bar__parent_node span{display:inline-block}#wp-admin-bar-cleantalk_admin_bar__parent_node .spbc-icon-attention-alt{background:#d63638;color:#fff;border-radius:50%;font-size:12px}#wp-admin-bar-cleantalk_admin_bar__parent_node img.cleantalk_admin_bar__spbc_icon{width:14px;height:17px;margin-top:7px}#wp-admin-bar-cleantalk_admin_bar__parent_node img.cleantalk_admin_bar__apbct_icon{width:18px;height:18px;margin-top:7px}#wp-admin-bar-cleantalk_admin_bar__parent_node div.cleantalk_admin_bar__sum_counter{color:#999;display:inline;padding:2px 5px !important}#wp-admin-bar-spbc_admin_bar__settings_link a,.cleantalk-admin_bar__parent a{display:inline-block !important;padding:0 10px !important}.cleantalk_admin_bar__blocked div{cursor:not-allowed !important}.cleantalk_admin_bar__blocked div a{color:#777 !important}b.spbc-admin_bar--online_admins{font-size:11px !important}.cleantalk_admin_bar__title{vertical-align:top;margin-left:5px !important}.clntlk-hint{position:relative !important}.clntlk-hint:hover{color:white}.clntlk-hint:hover::after{content:attr(hint);position:absolute;left:100%;top:50%;background:rgba(235,235,235,0.85);padding:0 5px;border:1px solid #333;color:black;font-style:normal;width:max-content;z-index:99999999999999999}.cleantalk_admin_bar__separator{height:0 !important}.cleantalk_admin_bar__counter a{padding:0 10px !important}.cleantalk-admin_bar--list_wrapper .ab-sub-wrapper ul:last-child{margin-bottom:5px !important}span.red_dot{height:8px !important;width:8px !important;background-color:#d00 !important;border-radius:50% !important;display:inline-block !important;margin-left:5px !important}span.green_dot{height:8px !important;width:8px !important;background-color:#0d0 !important;border-radius:50% !important;display:inline-block !important;margin-left:5px !important}#spbct-upload-checker-details{overflow-y:scroll;height:150px;border:solid #CCC 1px}#spbct-upload-checker-details li:nth-child(2n+1){background:#CCC}/*--------Vulnerability alarm section--------*/.plugins .update + .spbc-vulnerable-notice td{box-shadow:none !important}.themes .spbc-vulnerable-notice{top:auto;bottom:45px}/*Badge itself*/.spbct-safe-extension-badge{cursor:pointer}.spbct-safe-extension-badge[data-type="installed_plugins"]{display:inline-flex;position:relative;min-width:100px}/*Description placeholder*/.spbct-safe-extension-badge-description-placeholder{position:relative}.spbct-safe-extension-badge-icon-samll{width:16px !important;height:auto !important}/*Description body*/.spbct-safe-extension-badge-description{display:none;position:absolute;min-width:400px;background:rgba(0, 0, 0, 0.5);color:white;text-align:center;top:2px;padding:10px;animation:fadeInFromNone 0.3s ease-in-out;z-index:9999;width:max-content}.spbct-safe-extension-badge-description[data-type="installed_plugins"]{top:20px;left:-18px}.spbct-safe-extension-badge-description[data-type="before_install"]{top:-1px;right:0}/*Description body angle*/.spbct-safe-extension-badge-description:before{content:'';position:absolute;top:-9px;border:5px solid transparent;border-bottom:5px solid rgba(0, 0, 0, 0.5)}.spbct-safe-extension-badge-description[data-type="installed_plugins"]:before{left:6px}.spbct-safe-extension-badge-description[data-type="before_install"]:before{right:6px}/*Description body link style*/.spbct-safe-extension-badge-description > a{color:white;text-decoration:underline}/*Description popup behavior for list of installed plugins*/.spbct-safe-extension-badge[data-type="installed_plugins"]:hover > .spbct-safe-extension-badge-description-placeholder[data-type="installed_plugins"] > .spbct-safe-extension-badge-description[data-type="installed_plugins"],.spbct-safe-extension-badge-description[data-type="installed_plugins"]:hover{display:block}/*Description popup behavior for search*/.spbct-safe-extension-badge[data-type="before_install"]:hover + .spbct-safe-extension-badge-description-placeholder[data-type="before_install"] > .spbct-safe-extension-badge-description[data-type="before_install"],.spbct-safe-extension-badge-description[data-type="before_install"]:hover{display:block}@media screen and (max-width: 600px){.spbct-safe-extension-badge-description[data-type="installed_plugins"]{min-width:unset;width:unset}}/*Description popup animation rules*/@keyframes fadeInFromNone{0%{display:none;opacity:0}1%{display:block;opacity:0}100%{display:block;opacity:1}}#menu-settings{position:relative}.spbc_ring-container{position:absolute;z-index:99;top:-10px;right:10px}.spbc_circle{width:4px;height:4px;background-color:rgba(255, 2, 2, 0.65);border-radius:50%;position:absolute;top:15px;left:15px}.spbc_ringring{border:3px solid rgba(255, 2, 2, 0.65);-webkit-border-radius:30px;height:15px;width:15px;position:absolute;left:7px;top:7px;-webkit-animation:spbc_pulsate 1s ease-out;-webkit-animation-iteration-count:infinite;opacity:0.0}@-webkit-keyframes spbc_pulsate{0%{-webkit-transform:scale(0.1, 0.1);opacity:0.0}50%{opacity:1.0}100%{-webkit-transform:scale(1.2, 1.2);opacity:0.0}}.spbc-vulnerability-anchor{display:block;height:40vh;margin-top:-40vh;visibility:hidden}#spbc_scanner_copy_log_to_clipboard{position:relative}#spbc_scanner_copy_log_to_clipboard_hint{display:none;width:200px;position:absolute;top:20px;left:0}#spbc_log_actions_wrapper{margin-top:5px;display:flex;flex-direction:column;justify-content:flex-end}.spbc_log_actions_links{display:flex;justify-content:flex-end;width:100%;margin-top:5px}#spbcscan_results_log_preloader{visibility:hidden;position:absolute;align-items:center;display:flex;flex-direction:row;justify-content:center}.ui-dialog.spbc---top{z-index:9990 !important;left:160px !important}td.column-spbc_pass_check{display:flex;flex-direction:column}.row-spbc_pass_check{margin:5px 0}/*long desc module*/.spbc_long_description__show:hover{color:#aaaaaa;cursor:pointer}.spbc_long_desc{position:absolute;background:#5a5a5a;min-width:80px;min-height:80px;max-width:500px;padding:10px;color:white;z-index:10}@media screen and (max-width: 900px){.spbc_long_desc{left:inherit !important}.spbc_long_desc__angle_top_left{display:none}}.spbc_long_desc p{color:white !important}i.animate-spin{-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;display:inline-block;margin:0 auto;font-size:25px;line-height:20px;color:rgba(120,200,230,1);margin:25px}@keyframes spin{to{transform:rotate(359deg)}}.spbc_long_desc__cancel{position:absolute;top:5px;right:5px;color:rgba(255,255,255,0.5)}.spbc_long_desc__cancel:hover{color:white}.spbc_long_desc__angle{position:absolute;top:5px;left:-17px;width:10px;height:10px;background:#5a5a5a;-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg);-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.spbc_long_desc__angle_top_left{position:absolute;top:-10px;left:-9px;width:10px;height:10px;background:#5a5a5a;-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg);-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.spbc_long_desc__angle_top_right{position:absolute;top:-10px;right:14px;width:10px;height:10px;background:#5a5a5a;-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg);-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.spbc_long_desc__title{color:rgba(120,200,230,1);margin:0}.spbc_long_desc__link{color:rgba(120,200,230,1)}.spbc_long_desc__link:hover{color:rgba(140,220,250,1)}.spbc_long_desc h4{color:rgb(80,160,190);margin:10px 0 3px 0}.spbc_long_desc span{margin:0 0 0 10px;display:inline-block}.spbc_long_desc img{width:300px}.spbc-notice{padding:16px 32px !important}.spbc-notice .error{border-left-color:#BB3323 !important}.spbc-notice .notice-dismiss::before{color:#646464}.spbc-banner-error{background-color:#ffffff !important;box-shadow:0px 4px 15px #84848440 !important;border-left-color:#BB3323 !important}.spbc-banner-success{background-color:#ffffff !important;box-shadow:0px 4px 15px #84848440 !important;border-left-color:#00BB5D !important}.spbc-banner-content{display:flex;flex-wrap:wrap;justify-content:space-between;row-gap:16px}.spbc-banner-content-wrapper{display:flex;flex-wrap:wrap;gap:16px;justify-content:space-between}.spbc-banner-text-wrapper{margin-top:16px;margin-left:32px}.spbc-banner-button{display:block;padding:17px 53px;color:#FFFFFF !important;border-radius:8px;font-size:16px;font-weight:500;text-decoration:none !important;text-align:center;align-content:center}.spbc-banner-button-wrapper{display:grid;gap:12px;align-content:center;text-align:center;margin-right:24px;margin-left:32px}.spbc-banner-title{font-size:24px;line-height:29px;font-weight:500;color:#000000}.spbc-banner-subtitle{font-size:14px;line-height:22px;font-weight:400;color:#444444;margin-top:8px}.spbc-banner-big-subtitle{font-size:16px}.spbc-banner-link{font-size:16px;line-height:24px;font-weight:400;color:#026E88 !important}.spbc-banner-dismiss-link{font-weight:400;font-size:14px;line-height:22px;color:#646464 !important}.spbc-notice.spbc-banner-error{padding:16px 32px !important;background-color:#ffffff !important;box-shadow:0px 4px 15px #84848440 !important;border-left-color:#BB3323 !important}.spbc-notice.spbc-banner-error .notice-dismiss::before{color:#646464}.spbc-banner-button-green{background-color:#00BB5D !important}.spbc-banner-button-green:hover{background-color:#00a34f !important}.spbc-banner-button-red{background-color:#BB3323 !important}.spbc-banner-button-red:hover{background-color:#A82E20 !important} \ No newline at end of file +.spbc_meta_links{}.spbc_translate_links{color:rgba(150, 150, 20, 1)}.spbc_faq_links{color:rgba(20, 150, 20, 1)}.spbc_support_links{color:rgba(150, 20, 20, 1)}.spbc_setting_links{color:rgba(20, 20, 150, 1)}.spbc_translate_links:hover{color:rgba(210, 210, 20, 1) !important}.spbc_faq_links:hover{color:rgba(20, 250, 20, 1) !important}.spbc_support_links:hover{color:rgba(250, 20, 20, 1) !important}.spbc_setting_links:hover{color:rgba(20, 20, 250, 1) !important}.spbc_hide{display:none}.spbc_hide_table_cell_desc{display:none;max-width:90%}.--hide{display:none}.spbcRed{color:red}.ctlk---red{color:red}.spbcGreen{color:green}.spbcBlue{color:blue}.spbcGray{color:gray}.spbcTextCenter{text-align:center}.spbc-overflow--visible{overflow:visible !important}.-display--inline-block{display:inline-block}._animate--rotate{-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}#wp-admin-bar-cleantalk_admin_bar__parent_node{margin-right:5px}#wp-admin-bar-cleantalk_admin_bar__parent_node span{display:inline-block}#wp-admin-bar-cleantalk_admin_bar__parent_node .spbc-icon-attention-alt{background:#d63638;color:#fff;border-radius:50%;font-size:12px}#wp-admin-bar-cleantalk_admin_bar__parent_node img.cleantalk_admin_bar__spbc_icon{width:14px;height:17px;margin-top:7px}#wp-admin-bar-cleantalk_admin_bar__parent_node img.cleantalk_admin_bar__apbct_icon{width:18px;height:18px;margin-top:7px}#wp-admin-bar-cleantalk_admin_bar__parent_node div.cleantalk_admin_bar__sum_counter{color:#999;display:inline;padding:2px 5px !important}#wp-admin-bar-spbc_admin_bar__settings_link a,.cleantalk-admin_bar__parent a{display:inline-block !important;padding:0 10px !important}.cleantalk_admin_bar__blocked div{cursor:not-allowed !important}.cleantalk_admin_bar__blocked div a{color:#777 !important}b.spbc-admin_bar--online_admins{font-size:11px !important}.cleantalk_admin_bar__title{vertical-align:top;margin-left:5px !important}.clntlk-hint{position:relative !important}.clntlk-hint:hover{color:white}.clntlk-hint:hover::after{content:attr(hint);position:absolute;left:100%;top:50%;background:rgba(235,235,235,0.85);padding:0 5px;border:1px solid #333;color:black;font-style:normal;width:max-content;z-index:99999999999999999}.cleantalk_admin_bar__separator{height:0 !important}.cleantalk_admin_bar__counter a{padding:0 10px !important}.cleantalk-admin_bar--list_wrapper .ab-sub-wrapper ul:last-child{margin-bottom:5px !important}span.red_dot{height:8px !important;width:8px !important;background-color:#d00 !important;border-radius:50% !important;display:inline-block !important;margin-left:5px !important}span.green_dot{height:8px !important;width:8px !important;background-color:#0d0 !important;border-radius:50% !important;display:inline-block !important;margin-left:5px !important}#spbct-upload-checker-details{overflow-y:scroll;height:150px;border:solid #CCC 1px}#spbct-upload-checker-details li:nth-child(2n+1){background:#CCC}/*--------Vulnerability alarm section--------*/.plugins .update + .spbc-vulnerable-notice td{box-shadow:none !important}.themes .spbc-vulnerable-notice{top:auto;bottom:45px}/*Badge itself*/.spbct-safe-extension-badge{cursor:pointer}.spbct-safe-extension-badge[data-type="installed_plugins"]{display:inline-flex;position:relative;min-width:100px}/*Description placeholder*/.spbct-safe-extension-badge-description-placeholder{position:relative}.spbct-safe-extension-badge-icon-samll{width:16px !important;height:auto !important}/*Description body*/.spbct-safe-extension-badge-description{display:none;position:absolute;min-width:400px;background:rgba(0, 0, 0, 0.5);color:white;text-align:center;top:2px;padding:10px;animation:fadeInFromNone 0.3s ease-in-out;z-index:9999;width:max-content}.spbct-safe-extension-badge-description[data-type="installed_plugins"]{top:20px;left:-18px}.spbct-safe-extension-badge-description[data-type="before_install"]{top:-1px;right:0}/*Description body angle*/.spbct-safe-extension-badge-description:before{content:'';position:absolute;top:-9px;border:5px solid transparent;border-bottom:5px solid rgba(0, 0, 0, 0.5)}.spbct-safe-extension-badge-description[data-type="installed_plugins"]:before{left:6px}.spbct-safe-extension-badge-description[data-type="before_install"]:before{right:6px}/*Description body link style*/.spbct-safe-extension-badge-description > a{color:white;text-decoration:underline}/*Description popup behavior for list of installed plugins*/.spbct-safe-extension-badge[data-type="installed_plugins"]:hover > .spbct-safe-extension-badge-description-placeholder[data-type="installed_plugins"] > .spbct-safe-extension-badge-description[data-type="installed_plugins"],.spbct-safe-extension-badge-description[data-type="installed_plugins"]:hover{display:block}/*Description popup behavior for search*/.spbct-safe-extension-badge[data-type="before_install"]:hover + .spbct-safe-extension-badge-description-placeholder[data-type="before_install"] > .spbct-safe-extension-badge-description[data-type="before_install"],.spbct-safe-extension-badge-description[data-type="before_install"]:hover{display:block}@media screen and (max-width: 600px){.spbct-safe-extension-badge-description[data-type="installed_plugins"]{min-width:unset;width:unset}}/*Description popup animation rules*/@keyframes fadeInFromNone{0%{display:none;opacity:0}1%{display:block;opacity:0}100%{display:block;opacity:1}}#menu-settings{position:relative}.spbc_ring-container{position:absolute;z-index:99;top:-10px;right:10px}.spbc_circle{width:4px;height:4px;background-color:rgba(255, 2, 2, 0.65);border-radius:50%;position:absolute;top:15px;left:15px}.spbc_ringring{border:3px solid rgba(255, 2, 2, 0.65);-webkit-border-radius:30px;height:15px;width:15px;position:absolute;left:7px;top:7px;-webkit-animation:spbc_pulsate 1s ease-out;-webkit-animation-iteration-count:infinite;opacity:0.0}@-webkit-keyframes spbc_pulsate{0%{-webkit-transform:scale(0.1, 0.1);opacity:0.0}50%{opacity:1.0}100%{-webkit-transform:scale(1.2, 1.2);opacity:0.0}}.spbc-vulnerability-anchor{display:block;height:40vh;margin-top:-40vh;visibility:hidden}#spbc_scanner_copy_log_to_clipboard{position:relative}#spbc_scanner_copy_log_to_clipboard_hint{display:none;width:200px;position:absolute;top:20px;left:0}#spbc_log_actions_wrapper{margin-top:5px;display:flex;flex-direction:column;justify-content:flex-end}#spbc_log_actions_wrapper.spbc---hidden{display:none}.spbc_log_actions_links{display:flex;justify-content:flex-end;width:100%;margin-top:5px}#spbcscan_results_log_preloader{visibility:hidden;position:absolute;align-items:center;display:flex;flex-direction:row;justify-content:center}.ui-dialog.spbc---top{z-index:9990 !important;left:160px !important}td.column-spbc_pass_check{display:flex;flex-direction:column}.row-spbc_pass_check{margin:5px 0}/*long desc module*/.spbc_long_description__show:hover{color:#aaaaaa;cursor:pointer}.spbc_long_desc{position:absolute;background:#5a5a5a;min-width:80px;min-height:80px;max-width:500px;padding:10px;color:white;z-index:10}@media screen and (max-width: 900px){.spbc_long_desc{left:inherit !important}.spbc_long_desc__angle_top_left{display:none}}.spbc_long_desc p{color:white !important}i.animate-spin{-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear;display:inline-block;margin:0 auto;font-size:25px;line-height:20px;color:rgba(120,200,230,1);margin:25px}@keyframes spin{to{transform:rotate(359deg)}}.spbc_long_desc__cancel{position:absolute;top:5px;right:5px;color:rgba(255,255,255,0.5)}.spbc_long_desc__cancel:hover{color:white}.spbc_long_desc__angle{position:absolute;top:5px;left:-17px;width:10px;height:10px;background:#5a5a5a;-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg);-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.spbc_long_desc__angle_top_left{position:absolute;top:-10px;left:-9px;width:10px;height:10px;background:#5a5a5a;-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg);-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.spbc_long_desc__angle_top_right{position:absolute;top:-10px;right:14px;width:10px;height:10px;background:#5a5a5a;-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg);-webkit-transform-origin:100% 100%;-ms-transform-origin:100% 100%;transform-origin:100% 100%}.spbc_long_desc__title{color:rgba(120,200,230,1);margin:0}.spbc_long_desc__link{color:rgba(120,200,230,1)}.spbc_long_desc__link:hover{color:rgba(140,220,250,1)}.spbc_long_desc h4{color:rgb(80,160,190);margin:10px 0 3px 0}.spbc_long_desc span{margin:0 0 0 10px;display:inline-block}.spbc_long_desc img{width:300px}.spbc-notice{padding:16px 32px !important}.spbc-notice .error{border-left-color:#BB3323 !important}.spbc-notice .notice-dismiss::before{color:#646464}.spbc-banner-error{background-color:#ffffff !important;box-shadow:0px 4px 15px #84848440 !important;border-left-color:#BB3323 !important}.spbc-banner-success{background-color:#ffffff !important;box-shadow:0px 4px 15px #84848440 !important;border-left-color:#00BB5D !important}.spbc-banner-content{display:flex;flex-wrap:wrap;justify-content:space-between;row-gap:16px}.spbc-banner-content-wrapper{display:flex;flex-wrap:wrap;gap:16px;justify-content:space-between}.spbc-banner-text-wrapper{margin-top:16px;margin-left:32px}.spbc-banner-button{display:block;padding:17px 53px;color:#FFFFFF !important;border-radius:8px;font-size:16px;font-weight:500;text-decoration:none !important;text-align:center;align-content:center}.spbc-banner-button-wrapper{display:grid;gap:12px;align-content:center;text-align:center;margin-right:24px;margin-left:32px}.spbc-banner-title{font-size:24px;line-height:29px;font-weight:500;color:#000000}.spbc-banner-subtitle{font-size:14px;line-height:22px;font-weight:400;color:#444444;margin-top:8px}.spbc-banner-big-subtitle{font-size:16px}.spbc-banner-link{font-size:16px;line-height:24px;font-weight:400;color:#026E88 !important}.spbc-banner-dismiss-link{font-weight:400;font-size:14px;line-height:22px;color:#646464 !important}.spbc-notice.spbc-banner-error{padding:16px 32px !important;background-color:#ffffff !important;box-shadow:0px 4px 15px #84848440 !important;border-left-color:#BB3323 !important}.spbc-notice.spbc-banner-error .notice-dismiss::before{color:#646464}.spbc-banner-button-green{background-color:#00BB5D !important}.spbc-banner-button-green:hover{background-color:#00a34f !important}.spbc-banner-button-red{background-color:#BB3323 !important}.spbc-banner-button-red:hover{background-color:#A82E20 !important} \ No newline at end of file diff --git a/css/src/spbc-admin.css b/css/src/spbc-admin.css index ba906786e..855ae2d98 100644 --- a/css/src/spbc-admin.css +++ b/css/src/spbc-admin.css @@ -318,6 +318,10 @@ span.green_dot{ justify-content: flex-end; } +#spbc_log_actions_wrapper.spbc---hidden { + display: none; +} + .spbc_log_actions_links { display: flex; justify-content: flex-end; diff --git a/inc/spbc-scanner.php b/inc/spbc-scanner.php index 935d161df..bfe8cb93c 100644 --- a/inc/spbc-scanner.php +++ b/inc/spbc-scanner.php @@ -51,6 +51,14 @@ function spbc_scanner__launch() */ function spbc_scanner__controller($transaction_id = null, $stage = null, $offset = null, $amount = null) { + // Status polling must not run FSA in the same request — that hides fatals from the UI. + if ( function_exists('wp_doing_ajax') && wp_doing_ajax() ) { + $ajax_action = isset($_REQUEST['action']) ? (string) $_REQUEST['action'] : ''; + if ( in_array($ajax_action, array('spbc_scanner__getScannerStatus', 'spbc_scanner_clear'), true) ) { + return true; + } + } + //cron task provide a single parameter if (isset($transaction_id) && is_array($transaction_id)) { $stage = isset($transaction_id['stage']) ? $transaction_id['stage'] : null; @@ -96,6 +104,17 @@ function spbc_scanner__getScannerStatus() wp_send_json(array('error' => 'Nonce had been changed. Please, restart the scan.')); } + $runtime_error = \CleantalkSP\SpbctWP\Scanner\ScannerQueue::getRuntimeError(); + if ( $runtime_error !== null ) { + // Shown once during the scan. Reloads and later fatals of the same run stay silent. + \CleantalkSP\SpbctWP\Scanner\ScannerQueue::markRuntimeErrorShown(); + wp_send_json_success(array( + 'status' => 'error', + 'error' => $runtime_error['error'], + 'stage' => $runtime_error['stage'], + )); + } + if ( spbc_scanner__isScannerIsInProgress() ) { $stages = get_option('spbc_scanning_stages'); $current_cron_task = Cron::getTask('background_scan'); diff --git a/inc/spbc-settings.php b/inc/spbc-settings.php index bba9da98d..d46e0b8ef 100644 --- a/inc/spbc-settings.php +++ b/inc/spbc-settings.php @@ -1648,6 +1648,9 @@ function spbc_field_scanner() echo ScanningLogFacade::render(); } + // Clear/copy links: hidden until the scan log block is shown. + echo ScanningLogFacade::renderLogActions(); + echo '
'; echo spbc_bulk_actions_description(); diff --git a/js/public/spbct-react-bundle.js b/js/public/spbct-react-bundle.js index cd75fa7e0..23b8932d9 100644 --- a/js/public/spbct-react-bundle.js +++ b/js/public/spbct-react-bundle.js @@ -786,7 +786,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa \*********************************************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ScanButton)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\nfunction _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\nfunction _iterableToArrayLimit(r, l) { var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t[\"return\"] && (u = t[\"return\"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }\nfunction _arrayWithHoles(r) { if (Array.isArray(r)) return r; }\n\nvar __ = wp.i18n.__;\nfunction ScanButton(_ref) {\n var scannerStatus = _ref.scannerStatus,\n scannerError = _ref.scannerError,\n onScanStarted = _ref.onScanStarted;\n // True only while the spbc_scanner__start request is in flight, i.e. between the click\n // and the moment the backend confirms the scan has been (re)started. Kept separate from\n // scannerStatus since the latter reflects polling data that may lag behind the request.\n var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),\n _useState2 = _slicedToArray(_useState, 2),\n isStarting = _useState2[0],\n setIsStarting = _useState2[1];\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n // Stop polling on the scanner ended\n if (scannerStatus === 'end') {\n renderScannerInfo();\n }\n }, [scannerStatus]);\n\n // Once the polling picks up the new scan (or anything else changes the status), the\n // \"starting\" state is no longer needed.\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n if (scannerStatus === 'in_progress') {\n setIsStarting(false);\n }\n }, [scannerStatus]);\n var buttonText = isStarting ? __('Scanner is starting…', 'security-malware-firewall') : scannerStatus === 'in_progress' ? __('Scan in progress', 'security-malware-firewall') : __('Perform Scan', 'security-malware-firewall');\n var getButtonTitle = function getButtonTitle() {\n if (scannerError) {\n return __('Scanner is disabled. Please, check errors on the top of the settings.', 'security-malware-firewall');\n }\n if (isStarting) {\n return __('Scanner is starting, please wait.', 'security-malware-firewall');\n }\n if (scannerStatus === 'in_progress') {\n return __('Scanner is in progress. Please, wait until the current scan is finished.', 'security-malware-firewall');\n }\n return __('Click for start scanning.', 'security-malware-firewall');\n };\n var startScanner = function startScanner() {\n setIsStarting(true);\n var data = {\n action: 'spbc_scanner__start'\n };\n var params = {\n callback: function callback() {\n // Resume/restart status polling immediately so the UI (button, spinner,\n // progress bar) starts reacting to the newly launched scan right away,\n // instead of staying stale until the polling interval had already died\n // after a previous scan finished.\n onScanStarted === null || onScanStarted === void 0 || onScanStarted();\n },\n errorOutput: function errorOutput(msg) {\n setIsStarting(false);\n console.log(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n function renderScannerInfo() {\n var _document$querySelect, _document$getElementB;\n // Scanner finished: reset the spinner/button. The stages list and progress bar\n // are driven reactively via the scannerStatus/currentStage/completedStages props\n // in ScannerInfo, so no manual DOM manipulation is needed for them here.\n (_document$querySelect = document.querySelector('#spbc_perform_scan+.spbc_preloader')) === null || _document$querySelect === void 0 || _document$querySelect.removeAttribute('style');\n (_document$getElementB = document.getElementById('spbc_perform_scan')) === null || _document$getElementB === void 0 || _document$getElementB.removeAttribute('disabled');\n }\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", {\n style: {\n textAlign: 'center',\n marginTop: '1em'\n }\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"button\", {\n id: \"spbc_perform_scan\",\n className: \"spbc_manual_link_scan\",\n type: \"button\",\n onClick: startScanner,\n disabled: isStarting || scannerStatus === 'in_progress' || scannerError,\n title: getButtonTitle()\n }, buttonText), (isStarting || scannerStatus === 'in_progress') && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"img\", {\n className: \"spbc_preloader\",\n src: \"/wp-content/plugins/security-malware-firewall/images/preloader.gif\",\n style: {\n display: 'inline-block'\n }\n })));\n}\n\n//# sourceURL=webpack:///./js/src/react/components/TabContent/TabScanner/ScannerInfo/ScanButton.js?\n}"); +eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ ScanButton)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\nfunction _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\nfunction _iterableToArrayLimit(r, l) { var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t[\"return\"] && (u = t[\"return\"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }\nfunction _arrayWithHoles(r) { if (Array.isArray(r)) return r; }\n\nvar __ = wp.i18n.__;\nfunction ScanButton(_ref) {\n var scannerStatus = _ref.scannerStatus,\n scannerError = _ref.scannerError,\n onScanStarted = _ref.onScanStarted;\n // True only while the spbc_scanner__start request is in flight, i.e. between the click\n // and the moment the backend confirms the scan has been (re)started. Kept separate from\n // scannerStatus since the latter reflects polling data that may lag behind the request.\n var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),\n _useState2 = _slicedToArray(_useState, 2),\n isStarting = _useState2[0],\n setIsStarting = _useState2[1];\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n // Stop polling on the scanner ended\n if (scannerStatus === 'end') {\n renderScannerInfo();\n }\n }, [scannerStatus]);\n\n // Once the polling picks up the new scan (or anything else changes the status), the\n // \"starting\" state is no longer needed.\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n if (scannerStatus === 'in_progress' || scannerStatus === 'error') {\n setIsStarting(false);\n }\n }, [scannerStatus]);\n var buttonText = isStarting ? __('Scanner is starting…', 'security-malware-firewall') : scannerStatus === 'in_progress' ? __('Scan in progress', 'security-malware-firewall') : __('Perform Scan', 'security-malware-firewall');\n var getButtonTitle = function getButtonTitle() {\n if (scannerError) {\n return __('Scanner is disabled. Please, check errors on the top of the settings.', 'security-malware-firewall');\n }\n if (isStarting) {\n return __('Scanner is starting, please wait.', 'security-malware-firewall');\n }\n if (scannerStatus === 'in_progress') {\n return __('Scanner is in progress. Please, wait until the current scan is finished.', 'security-malware-firewall');\n }\n return __('Click for start scanning.', 'security-malware-firewall');\n };\n var startScanner = function startScanner() {\n setIsStarting(true);\n var data = {\n action: 'spbc_scanner__start'\n };\n var params = {\n callback: function callback() {\n // Resume/restart status polling immediately so the UI (button, spinner,\n // progress bar) starts reacting to the newly launched scan right away,\n // instead of staying stale until the polling interval had already died\n // after a previous scan finished.\n onScanStarted === null || onScanStarted === void 0 || onScanStarted();\n },\n errorOutput: function errorOutput(msg) {\n setIsStarting(false);\n console.log(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n function renderScannerInfo() {\n var _document$querySelect, _document$getElementB;\n // Scanner finished: reset the spinner/button. The stages list and progress bar\n // are driven reactively via the scannerStatus/currentStage/completedStages props\n // in ScannerInfo, so no manual DOM manipulation is needed for them here.\n (_document$querySelect = document.querySelector('#spbc_perform_scan+.spbc_preloader')) === null || _document$querySelect === void 0 || _document$querySelect.removeAttribute('style');\n (_document$getElementB = document.getElementById('spbc_perform_scan')) === null || _document$getElementB === void 0 || _document$getElementB.removeAttribute('disabled');\n }\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", {\n style: {\n textAlign: 'center',\n marginTop: '1em'\n }\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"button\", {\n id: \"spbc_perform_scan\",\n className: \"spbc_manual_link_scan\",\n type: \"button\",\n onClick: startScanner,\n disabled: isStarting || scannerStatus === 'in_progress' || scannerError,\n title: getButtonTitle()\n }, buttonText), (isStarting || scannerStatus === 'in_progress') && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"img\", {\n className: \"spbc_preloader\",\n src: \"/wp-content/plugins/security-malware-firewall/images/preloader.gif\",\n style: {\n display: 'inline-block'\n }\n })));\n}\n\n//# sourceURL=webpack:///./js/src/react/components/TabContent/TabScanner/ScannerInfo/ScanButton.js?\n}"); /***/ }), @@ -806,7 +806,7 @@ eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpa \*********************************************************************/ /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { -eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ TabScanner)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _styles_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.css */ \"./js/src/react/components/TabContent/TabScanner/styles.css\");\n/* harmony import */ var _TabContentTypes_Html_TabContentHtml__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../TabContentTypes/Html/TabContentHtml */ \"./js/src/react/components/TabContentTypes/Html/TabContentHtml.js\");\n/* harmony import */ var _modules_Http_Ajax__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../modules/Http/Ajax */ \"./js/src/react/modules/Http/Ajax.js\");\n/* harmony import */ var _Preloader_Preloader__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../Preloader/Preloader */ \"./js/src/react/components/Preloader/Preloader.js\");\n/* harmony import */ var _NoticeBanner_NoticeBanner__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../NoticeBanner/NoticeBanner */ \"./js/src/react/components/TabContent/NoticeBanner/NoticeBanner.js\");\n/* harmony import */ var _ScannerInfo_ScannerInfo__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./ScannerInfo/ScannerInfo */ \"./js/src/react/components/TabContent/TabScanner/ScannerInfo/ScannerInfo.js\");\nfunction _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _iterableToArray(r) { if (\"undefined\" != typeof Symbol && null != r[Symbol.iterator] || null != r[\"@@iterator\"]) return Array.from(r); }\nfunction _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }\nfunction _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = \"function\" == typeof Symbol ? Symbol : {}, n = r.iterator || \"@@iterator\", o = r.toStringTag || \"@@toStringTag\"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, \"_invoke\", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError(\"Generator is already running\"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = \"next\"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError(\"iterator result is not an object\"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i[\"return\"]) && t.call(i), c < 2 && (u = TypeError(\"The iterator does not provide a '\" + o + \"' method\"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, \"GeneratorFunction\")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, \"constructor\", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, \"constructor\", GeneratorFunction), GeneratorFunction.displayName = \"GeneratorFunction\", _regeneratorDefine2(GeneratorFunctionPrototype, o, \"GeneratorFunction\"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, \"Generator\"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, \"toString\", function () { return \"[object Generator]\"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }\nfunction _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, \"\", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o(\"next\", 0), o(\"throw\", 1), o(\"return\", 2)); }, _regeneratorDefine2(e, r, n, t); }\nfunction asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }\nfunction _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, \"next\", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, \"throw\", n); } _next(void 0); }); }; }\nfunction _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\nfunction _iterableToArrayLimit(r, l) { var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t[\"return\"] && (u = t[\"return\"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }\nfunction _arrayWithHoles(r) { if (Array.isArray(r)) return r; }\n\n\n\n\n\n\n\nfunction TabScanner(_ref) {\n var sectionId = _ref.sectionId,\n sectionData = _ref.sectionData,\n isActive = _ref.isActive;\n var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null),\n _useState2 = _slicedToArray(_useState, 2),\n scannerStatus = _useState2[0],\n setScannerStatus = _useState2[1];\n var prevStatusRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);\n var _useState3 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null),\n _useState4 = _slicedToArray(_useState3, 2),\n currentStage = _useState4[0],\n setCurrentStage = _useState4[1];\n var _useState5 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]),\n _useState6 = _slicedToArray(_useState5, 2),\n completedStages = _useState6[0],\n setCompletedStages = _useState6[1];\n // Progress (0-100%) of the currently running stage only, plus the raw offset/total it was\n // computed from. `null` percent means the backend couldn't determine a total for this stage\n // (e.g. quick stages like clean_results), so the UI shows an indeterminate progress state.\n var _useState7 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({\n offset: null,\n total: null,\n percent: null\n }),\n _useState8 = _slicedToArray(_useState7, 2),\n currentStageProgress = _useState8[0],\n setCurrentStageProgress = _useState8[1];\n var intervalRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);\n // Tracks whether the previous poll tick saw the scanner running, so we know to do one last\n // log refresh right after it finishes (the last tick's fetch may have missed the very last\n // rows written while the \"send_results\"/cure stages were wrapping up).\n var scanWasInProgressRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);\n\n // Pulls the freshest scan log rows (written server-side to the DB by the background scan,\n // see CleantalkSP\\SpbctWP\\Scanner\\ScanningLog\\Repository::write()) and renders them into the\n // live \".spbc_log-wrapper\" block. This reuses the same AJAX action the historical, paginated\n // \"#spbcscan-results-log-module\" log uses to lazy-load older rows on scroll; here it's always\n // called with offset 0 to get the latest rows (Repository::getAll() orders by timestamp DESC).\n var fetchScanLog = function fetchScanLog() {\n jQuery.ajax({\n url: spbcSettings.ajaxurl,\n type: 'POST',\n data: {\n action: 'spbc_scanner_load_more_scan_logs',\n security: spbcSettings.ajax_nonce,\n no_cache: Math.random(),\n offset: 0\n },\n success: function success(data) {\n if (!data || !data.success || !data.data) {\n return;\n }\n if (!jQuery('.spbc_log-wrapper .panel-body').length) {\n jQuery('.spbc_log-wrapper').append('
');\n }\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper .panel-body').html(data.data);\n }\n });\n };\n var fetchScannerStatus = function fetchScannerStatus() {\n var data = {\n action: 'spbc_scanner__getScannerStatus'\n };\n var params = {\n callback: scannerStatusCallback,\n errorOutput: function errorOutput(msg) {\n console.log(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n // (Re)starts the polling interval. Used on mount and whenever a new scan is kicked off,\n // so polling reliably resumes even if it had been stopped after a previous scan ended.\n var startPolling = function startPolling() {\n clearInterval(intervalRef.current);\n\n // First request do immediately\n fetchScannerStatus();\n\n // Start polling to fetch the scanner state\n intervalRef.current = setInterval(fetchScannerStatus, 5000);\n };\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n // Stop polling on the scanner ended\n if (scannerStatus === 'end') {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n // Reload page during status changed in_progress -> end\n if (prevStatusRef.current === 'in_progress' && scannerStatus === 'end') {\n window.location.reload();\n }\n prevStatusRef.current = scannerStatus;\n }, [scannerStatus]);\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n window.spbcScanner = window.spbcScanner || {};\n window.spbcScanner.active = false;\n startPolling();\n // Cleanup on unmount\n return function () {\n return clearInterval(intervalRef.current);\n };\n }, []);\n function scannerStatusCallback(result) {\n var _result$data;\n if (result && result !== null && result !== void 0 && (_result$data = result.data) !== null && _result$data !== void 0 && _result$data.status) {\n var _result$data2, _result$data3, _result$data$current_, _result$data4, _result$data$current_2, _result$data5, _result$data$current_3, _result$data6;\n var status = result.data.status;\n setScannerStatus(status);\n setCurrentStage((result === null || result === void 0 || (_result$data2 = result.data) === null || _result$data2 === void 0 || (_result$data2 = _result$data2.current_stage) === null || _result$data2 === void 0 ? void 0 : _result$data2.stage) || null);\n setCompletedStages(((result === null || result === void 0 || (_result$data3 = result.data) === null || _result$data3 === void 0 ? void 0 : _result$data3.completed_stages) || []).map(function (stage) {\n return stage.stage;\n }));\n setCurrentStageProgress({\n offset: (_result$data$current_ = result === null || result === void 0 || (_result$data4 = result.data) === null || _result$data4 === void 0 || (_result$data4 = _result$data4.current_stage) === null || _result$data4 === void 0 ? void 0 : _result$data4.offset) !== null && _result$data$current_ !== void 0 ? _result$data$current_ : null,\n total: (_result$data$current_2 = result === null || result === void 0 || (_result$data5 = result.data) === null || _result$data5 === void 0 || (_result$data5 = _result$data5.current_stage) === null || _result$data5 === void 0 ? void 0 : _result$data5.total) !== null && _result$data$current_2 !== void 0 ? _result$data$current_2 : null,\n percent: (_result$data$current_3 = result === null || result === void 0 || (_result$data6 = result.data) === null || _result$data6 === void 0 || (_result$data6 = _result$data6.current_stage) === null || _result$data6 === void 0 ? void 0 : _result$data6.percent) !== null && _result$data$current_3 !== void 0 ? _result$data$current_3 : null\n });\n if (status === 'in_progress') {\n scanWasInProgressRef.current = true;\n window.spbcScanner.active = true;\n fetchScanLog();\n } else if (scanWasInProgressRef.current) {\n // Scan just finished: refresh once more to catch the last rows written\n // while the scan was wrapping up (e.g. the \"send_results\" stage duration entry).\n scanWasInProgressRef.current = false;\n window.spbcScanner.active = false;\n fetchScanLog();\n }\n } else {\n // Stop polling if the wrong answer coming\n clearInterval(intervalRef.current);\n setScannerStatus(null);\n setCurrentStage(null);\n setCompletedStages([]);\n setCurrentStageProgress({\n offset: null,\n total: null,\n percent: null\n });\n }\n }\n\n /** **************** SUPPORT TEMPORARY BOTH CONTENT FETCHED - LEGACY AND NEW *****************/\n var _useState9 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({\n sectionData: sectionData\n }),\n _useState0 = _slicedToArray(_useState9, 2),\n sectionDataLegacy = _useState0[0],\n setSectionDataLegacy = _useState0[1];\n var _useState1 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),\n _useState10 = _slicedToArray(_useState1, 2),\n scannerDataLegacyFetched = _useState10[0],\n setSectionDataLegacyFetched = _useState10[1];\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n fetchOldContent();\n }, []);\n var fetchOldContent = /*#__PURE__*/function () {\n var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {\n var requestData, data, _t;\n return _regenerator().w(function (_context) {\n while (1) switch (_context.p = _context.n) {\n case 0:\n _context.p = 0;\n requestData = {\n action: 'spbc_settings__draw_elements',\n tab_name: sectionId\n };\n _context.n = 1;\n return (0,_modules_Http_Ajax__WEBPACK_IMPORTED_MODULE_3__.sendAjaxRequest)(requestData);\n case 1:\n data = _context.v;\n setSectionDataLegacy(data);\n setSectionDataLegacyFetched(true);\n document.dispatchEvent(new CustomEvent('spbctTabLoaded', {\n bubbles: true,\n detail: {\n tab: sectionId\n }\n }));\n _context.n = 3;\n break;\n case 2:\n _context.p = 2;\n _t = _context.v;\n console.error('Error fetching content:', _t);\n setSectionDataLegacy('');\n setSectionDataLegacyFetched(true);\n case 3:\n return _context.a(2);\n }\n }, _callee, null, [[0, 2]]);\n }));\n return function fetchOldContent() {\n return _ref2.apply(this, arguments);\n };\n }();\n if (!scannerDataLegacyFetched) {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_Preloader_Preloader__WEBPACK_IMPORTED_MODULE_4__[\"default\"], null);\n }\n /** **************** END SUPPORT TEMPORARY BOTH CONTENT FETCHED *****************/\n\n /** **** L E G A C Y ******/\n\n /**\n * Show popup tip\n *\n * @param {object} showIcon\n */\n window.spbcPopupTipShow = function (showIcon) {\n // eslint-disable-line no-unused-vars\n var tipTitle = showIcon.attr('spbc_tip_title') || null;\n var tipText = showIcon.attr('spbc_tip_text') || null;\n var _removeDescFunc = function removeDescFunc(e) {\n if (typeof e === 'undefined' || jQuery(e.target).parent('.spbc_popup_tip--wrapper').length == 0 || jQuery(e.target).hasClass('spbc_popup_tip--icon---cancel')) {\n jQuery('.spbc_popup_tip--wrapper').hide();\n jQuery(document).off('click', _removeDescFunc);\n }\n };\n _removeDescFunc();\n showIcon.after('
');\n var obj = jQuery('.spbc_popup_tip--wrapper');\n obj.empty().append('
').css({\n top: showIcon.position().top - 2,\n left: showIcon.position().left + 25\n });\n obj.append('').append('

' + tipTitle + '

').append('

' + tipText + '

');\n jQuery(document).on('click', _removeDescFunc);\n showIcon.parents('tr').on('mouseout', _removeDescFunc);\n };\n\n /**\n * View file in the scanner\n *\n * @param {object} obj\n */\n window.spbcScannerButtonFileViewEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_file_view',\n file_id: self.parents('.row-actions').attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n document.addEventListener('spbcModalClosed', function (e) {\n spbcReloadAccordion();\n });\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Delete file from analysis log\n *\n * @param {object} obj\n */\n window.spbcScannerAnalysisLogDeleteFromLog = function (obj) {\n // eslint-disable-line no-unused-vars\n var fileIds = [];\n var self = jQuery(obj);\n var spinner;\n if (jQuery(obj).hasClass('tbl-row_action')) {\n fileIds.push(self.parents('.row-actions').attr('uid'));\n spinner = self.parent().siblings('.tbl-preloader--tiny');\n } else {\n var selectedItems = self.closest('#spbc_tbl__scanner_analysis_log').find('.cb-select:checked');\n if (selectedItems.length === 0) {\n alert('Please, select elements.');\n return;\n }\n selectedItems.each(function (index, element) {\n var elementId = jQuery(element).val();\n fileIds.push(elementId);\n });\n spinner = self.children('.tbl-preloader--small');\n }\n var data = {\n action: 'spbc_scanner_analysis_log_delete_from_log',\n file_ids: fileIds\n };\n var params = {\n spinner: spinner,\n callback: function callback() {\n window.location.reload();\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * View page in the scanner\n *\n * @param {object} obj\n */\n window.spbcScannerButtonPageViewEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_page_view',\n page_url: self.parents('.row-actions').attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n }\n };\n var wpWrap = jQuery('#wpwrap');\n var dialogWindow = jQuery('#spbc_dialog');\n dialogWindow.dialog({\n modal: true,\n title: 'Loading..',\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(wpWrap.width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: false,\n resizable: false,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = 'auto';\n jQuery('#spbc_dialog').height(document.documentElement.clientHeight / 100 * 25);\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n }\n });\n dialogWindow.append('\"Wait');\n var spinner = jQuery('#spbc_file_view_preloader');\n var sizeMultiplier = wpWrap.width() * 0.0004;\n spinner.height(128 * sizeMultiplier);\n spinner.width(128 * sizeMultiplier);\n spinner.css({\n left: dialogWindow.width() / 2 - 128 * sizeMultiplier / 2\n });\n spinner.css({\n top: dialogWindow.height() / 2 - 128 * sizeMultiplier / 2\n });\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Callback for file view\n *\n * @param {object} result\n */\n function spbcScannerButtonViewCallback(result) {\n // eslint-disable-line no-unused-vars\n var dialogWindow = jQuery('#spbc_dialog');\n dialogWindow.empty();\n jQuery('#spbc_file_view_preloader').css({\n display: 'none'\n });\n var rowTemplate = '
%s';\n rowTemplate += '

%s


';\n\n /* eslint-disable guard-for-in */\n for (var row in result.file) {\n dialogWindow.append(rowTemplate.printf(row, result.file[row]));\n }\n var contentHeight = Object.keys(result.file).length * 19 < 76 ? 76 : Object.keys(result.file).length * 19;\n var visibleHeight = document.documentElement.clientHeight / 100 * 75;\n var overflow = contentHeight < visibleHeight ? 'hidden' : 'scroll';\n var height = overflow === 'scroll' ? visibleHeight : contentHeight;\n dialogWindow.css({\n height: height,\n overflow: overflow\n });\n dialogWindow.dialog({\n modal: true,\n title: 'Loaded: ' + result.file_path,\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(jQuery('#wpwrap').width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: true,\n resizable: false,\n height: height,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = overflow;\n jQuery('#spbc_dialog').height(height);\n jQuery('.ui-widget-overlay').on('click', function () {\n jQuery('#spbc_dialog').dialog('close');\n });\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n }\n });\n console.log('FILE_VIEWED');\n }\n\n /**\n * Event for file view bad\n *\n * @param {object} obj\n */\n window.spbcScannerButtonFileViewBadEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_file_view',\n file_id: self.parents('.row-actions').attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewBadFileCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Event for page view bad\n *\n * @param {object} obj\n */\n window.spbcScannerButtonPageViewBadEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_page_view',\n page_url: self.parent().attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewBadCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Callback for file view bad\n *\n * @param {object} result\n * @param {object} data\n * @param {object} params\n */\n function spbcScannerButtonViewBadFileCallback(result, data, params) {\n // eslint-disable-line no-unused-vars\n console.log('FILE_VIEWED_BAD_CODE_FILE');\n jQuery('#spbc_dialog').empty();\n var rowTemplate = '
%s';\n rowTemplate += '

%s


';\n var rowTemplateBad = '
';\n rowTemplateBad += '%s';\n rowTemplateBad += '

%s


';\n var severityParts = [];\n var severityPartsSorted = [];\n var severityKeysSorted = [];\n result.weak_spots = JSON.parse(result.weak_spots);\n /* eslint-disable guard-for-in */\n for (var severity in result.weak_spots) {\n severityParts[severity] = Object.keys(result.weak_spots[severity]);\n }\n severityKeysSorted = Object.keys(severityParts).sort();\n\n /* eslint-disable guard-for-in */\n for (var key in severityKeysSorted) {\n severityPartsSorted[severityKeysSorted[key]] = severityParts[severityKeysSorted[key]];\n }\n\n /* eslint-disable guard-for-in */\n for (var severityPart in severityPartsSorted) {\n var headerName = 'spbc-dialog-row-header-' + severityPart.toLowerCase();\n var headerHtml = '';\n headerHtml += severityPart + ' weakspots:';\n var dialog = jQuery('#spbc_dialog');\n dialog.append(rowTemplate.printf('', headerHtml));\n dialog.append(rowTemplate.printf('...', '...'));\n var header = dialog.find('#' + headerName);\n header.parent().parent().css('text-align', 'center');\n header.parent().parent().css('border', '1px dotted');\n header.parent().parent().css('background', 'darkgrey');\n\n /* eslint-disable guard-for-in */\n for (var strNum in severityParts[severityPart]) {\n // Sort bad line\n strNum = _toConsumableArray(new Set(strNum)).sort();\n var row = +severityParts[severityPart][strNum];\n var displayDepth = 2;\n var temp = void 0;\n // Display only existing lines of file\n for (var currRow = row - displayDepth; currRow <= row + displayDepth; currRow++) {\n if (typeof result.file[currRow] !== 'undefined') {\n temp = row === currRow ? rowTemplateBad : rowTemplate;\n dialog.append(temp.printf(currRow, result.file[currRow]));\n }\n }\n\n // Placing an empty string between results\n dialog.append(rowTemplate.printf('...', '...'));\n }\n }\n\n // Removing last empty string\n jQuery('#spbc_dialog div').last().remove();\n var contentHeight = jQuery('#spbc_dialog div').length * 19 < 76 ? 76 : jQuery('#spbc_dialog div').length * 19;\n var visibleHeight = document.documentElement.clientHeight / 100 * 75;\n var overflow = contentHeight < visibleHeight ? 'no_scroll' : 'scroll';\n var height = overflow === 'scroll' ? visibleHeight : contentHeight;\n jQuery('#spbc_dialog').dialog({\n modal: true,\n title: result.file_path,\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(jQuery('#wpwrap').width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: true,\n resizable: false,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = overflow;\n jQuery('#spbc_dialog').height(height);\n jQuery('.ui-widget-overlay').on('click', function () {\n jQuery('#spbc_dialog').dialog('close');\n });\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n }\n });\n }\n\n /**\n * Callback for file view bad\n *\n * @param {object} result\n * @param {object} data\n * @param {object} params\n */\n function spbcScannerButtonViewBadCallback(result, data, params) {\n // eslint-disable-line no-unused-vars\n console.log('FILE_VIEWED_BAD_CODE');\n var afterClose = function afterClose() {};\n if (result.success == true) {\n console.log(result);\n var rowTemplate = '
' + '%s' + '

%s

' + '
';\n var rowTemplateBad = '
' + '%s' + '

%s

' + '
';\n var strNums = [];\n result.weak_spots = JSON.parse(result.weak_spots);\n /* eslint-disable guard-for-in */\n for (var severity in result.weak_spots) {\n strNums = strNums.concat(Object.keys(result.weak_spots[severity]));\n }\n strNums = _toConsumableArray(new Set(strNums)).sort(); // Sort bad line\n // get a map of bad strings\n // TODO: Probably there can get something else except CRITICAL in the future\n var badStringsMap = new Map(Object.entries(result.weak_spots.CRITICAL));\n /* eslint-disable guard-for-in */\n for (var strNum in strNums) {\n if (strNum == 0 && badStringsMap.get(strNums[strNum]).toLowerCase().includes('doctype')) {\n continue;\n }\n var row = +strNums[strNum];\n var displayDepth = 2;\n\n // Display only existing lines of file\n for (var currRow = row - displayDepth; currRow <= row + displayDepth; currRow++) {\n if (typeof result.file[currRow] !== 'undefined') {\n var isBadString = row === currRow;\n var unformattedRow = void 0;\n var highlitedRow = void 0;\n if (isBadString) {\n unformattedRow = rowTemplateBad;\n } else {\n unformattedRow = rowTemplate;\n }\n if (isBadString && !result.file[currRow].includes('iframe')) {\n highlitedRow = unformattedRow.printf(currRow, badStringsMap.get(strNums[strNum]));\n } else {\n highlitedRow = unformattedRow.printf(currRow, result.file[currRow]);\n }\n jQuery('#spbc_dialog').append(highlitedRow);\n }\n }\n\n // Placing an empty string between results\n jQuery('#spbc_dialog').append(rowTemplate.printf('', ''));\n\n // Removing last empty string\n jQuery('#spbc_dialog div').last().remove();\n }\n } else {\n var wrapperElement = '
%s
';\n jQuery('#spbc_dialog').append(wrapperElement.printf(result.content));\n afterClose = function afterClose() {\n document.location.reload();\n };\n }\n var contentHeight = jQuery('#spbc_dialog div').length * 19 < 76 ? 76 : jQuery('#spbc_dialog div').length * 19;\n var visibleHeight = document.documentElement.clientHeight / 100 * 75;\n var height = 'auto';\n var overflow = contentHeight < visibleHeight ? 'no_scroll' : 'scroll';\n jQuery('#spbc_dialog').dialog({\n modal: true,\n title: result.file_path,\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(jQuery('#wpwrap').width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: true,\n resizable: false,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = overflow;\n jQuery('#spbc_dialog').height(height);\n jQuery('.ui-widget-overlay').on('click', function () {\n jQuery('#spbc_dialog').dialog('close');\n });\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n },\n close: afterClose\n });\n }\n\n /**\n * Cure file\n * @param {object} obj\n */\n window.spbcScannerButtonCureFileAjaxHandler = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var fileFastHash = self.parents('.row-actions').attr('uid');\n var data = {\n action: 'spbc_file_cure_ajax_action',\n file_fast_hash: fileFastHash,\n security: spbcSettings.ajax_nonce\n };\n jQuery.ajax({\n type: 'POST',\n async: true,\n url: spbcSettings.ajaxurl,\n data: data,\n success: function success(result) {\n if (result.success) {\n spbcModal.open().put(result.data);\n document.addEventListener('spbcModalClosed', function (e) {\n document.location.reload();\n });\n } else {\n spbcModal.open().putError(result.data);\n }\n }\n });\n };\n\n /**\n * Restore file from backup\n * @param {object} obj\n */\n window.spbcScannerButtonRestoreFromBackupAjaxHandler = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var fileFastHash = self.parents('.row-actions').attr('uid');\n var data = {\n action: 'spbc_restore_file_from_backup_ajax_action',\n file_fast_hash: fileFastHash,\n security: spbcSettings.ajax_nonce\n };\n jQuery.ajax({\n type: 'POST',\n async: true,\n url: spbcSettings.ajaxurl,\n data: data,\n success: function success(result) {\n if (result.success) {\n spbcModal.open().put(result.data);\n } else {\n spbcModal.open().putError(result.data);\n }\n document.addEventListener('spbcModalClosed', function (e) {\n document.location.reload();\n });\n }\n });\n };\n\n /**\n * Show copy log to clipboard hint\n * @param {string} text\n */\n function spbcScannerCopyLogToClipboardHint(text) {\n jQuery('#spbc_scanner_copy_log_to_clipboard_hint').text(text);\n jQuery('#spbc_scanner_copy_log_to_clipboard_hint').show();\n jQuery('#spbc_scanner_copy_log_to_clipboard_hint').animate({\n opacity: 0\n }, 3000, function () {\n jQuery(this).hide();\n jQuery(this).css('opacity', 1);\n });\n }\n jQuery(document).ready(function () {\n // EVENT HADLING\n spbcTblBulkActionsListen(); // Table. Row bulk handler\n spbcTblRowActionsListen(); // Table. Row actions handler\n spbcTblPaginationListen(); // Table. Pagination handler\n spbcTblSortListen(); // Table. Sort handler\n\n spbcStartShowHide();\n jQuery(document).on('click', '.spbc_popup_tip--icon---show', function () {\n spbcPopupTipShow(jQuery(this));\n });\n jQuery('#spbc_perform_scan').on('click', function () {\n jQuery('#spbcscan-results-log-module').remove();\n // spbcScanner.actionControl();\n\n if (!jQuery('.spbc_log-wrapper .panel-body').length) {\n jQuery('.spbc_log-wrapper').append('
');\n }\n jQuery('.spbc_log-wrapper').resizable({\n handles: 'n, s',\n containment: 'document'\n });\n });\n jQuery('#spbcscan-results-log-module').resizable({\n handles: 'n, s',\n containment: 'document'\n });\n logActionsHandleClearLogs();\n logActionsHandleMoreLogs();\n logActionsHandleCopyToClipboard();\n });\n function logActionsHandleClearLogs() {\n // Clear scanner's tables\n jQuery('#spbc_scanner_clear').on('click', function () {\n // eslint-disable-line no-unused-vars\n if (!confirm(spbcSettings.clear_results_confirm)) {\n return;\n }\n spbcSendAJAXRequest({\n action: 'spbc_scanner_clear'\n }, {\n type: 'GET',\n successCallback: function successCallback() {\n document.location = document.location;\n },\n complete: null,\n errorOutput: null,\n context: this,\n timeout: 40000,\n success: function success() {\n document.location.reload();\n }\n });\n });\n }\n function logActionsHandleCopyToClipboard() {\n if (jQuery('#spbc_scanner_copy_log_to_clipboard').length) {\n jQuery('#spbc_scanner_copy_log_to_clipboard').on('click', function (e) {\n e.preventDefault();\n if (navigator.clipboard) {\n var log = jQuery('#spbcscan-results-log-module .panel-body').html();\n var formattedLog = log.replace(//g, '').replace(/<\\/b>/g, '');\n formattedLog = formattedLog.replace(/

/g, '\\n').replace(/<\\/p>/g, '');\n navigator.clipboard.writeText(formattedLog).then(function () {\n spbcScannerCopyLogToClipboardHint(spbcScaner.copy_log_to_clipboard_hint);\n })[\"catch\"](function (err) {\n spbcScannerCopyLogToClipboardHint(spbcScaner.copy_log_to_clipboard_hint_failed);\n });\n } else {\n spbcScannerCopyLogToClipboardHint(spbcScaner.copy_log_to_clipboard_hint_unsupported);\n }\n });\n }\n }\n function logActionsHandleMoreLogs() {\n var element = jQuery('#spbcscan_results_log_body');\n var logAppendPreloader = jQuery('#spbcscan_results_log_preloader');\n if (!element || !logAppendPreloader) {\n return;\n }\n element.on('scroll', function () {\n // Check if load completed\n if (jQuery(this).data('stop-load')) {\n return;\n }\n\n // Is end of scroll\n var isAtBottom = this.scrollHeight - this.scrollTop <= this.clientHeight + 1;\n if (isAtBottom) {\n var $this = jQuery(this);\n var currentOffset = parseInt($this.data('offset')) || 0;\n logAppendPreloader.css('visibility', 'visible');\n jQuery.ajax({\n url: spbcSettings.ajaxurl,\n type: 'POST',\n data: {\n action: 'spbc_scanner_load_more_scan_logs',\n security: spbcSettings.ajax_nonce,\n no_cache: Math.random(),\n offset: currentOffset\n },\n xhrFields: {\n responseType: 'text'\n },\n success: function success(data) {\n if (data.success && data.data) {\n // increase and set new offset\n var newOffset = currentOffset + 100;\n $this.data('offset', newOffset);\n\n // add html to the log\n jQuery('#spbcscan_results_log_body').append(data.data);\n\n // if no data - stop load\n if (data.data.length === 0 || data.data.trim() === '') {\n $this.data('stop-load', 1);\n }\n } else {\n $this.data('stop-load', 1);\n }\n logAppendPreloader.css('visibility', 'hidden');\n },\n error: function error(xhr, status, _error) {\n logAppendPreloader.css('visibility', 'hidden');\n $this.data('stop-load', 1);\n }\n });\n }\n });\n }\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", {\n className: \"spbc_wrapper_field\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", {\n className: 'spbc_tab_fields_group'\n }, window.spbcSettings && window.spbcSettings.critical > 0 && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_NoticeBanner_NoticeBanner__WEBPACK_IMPORTED_MODULE_5__[\"default\"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_ScannerInfo_ScannerInfo__WEBPACK_IMPORTED_MODULE_6__[\"default\"], {\n sectionData: sectionData,\n scannerStatus: scannerStatus,\n currentStage: currentStage,\n completedStages: completedStages,\n currentStageProgress: currentStageProgress,\n onScanStarted: startPolling\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_TabContentTypes_Html_TabContentHtml__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n sectionId: sectionId,\n sectionData: sectionDataLegacy,\n isActive: isActive\n })));\n}\n\n//# sourceURL=webpack:///./js/src/react/components/TabContent/TabScanner/TabScanner.js?\n}"); +eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ TabScanner)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _styles_css__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./styles.css */ \"./js/src/react/components/TabContent/TabScanner/styles.css\");\n/* harmony import */ var _TabContentTypes_Html_TabContentHtml__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../TabContentTypes/Html/TabContentHtml */ \"./js/src/react/components/TabContentTypes/Html/TabContentHtml.js\");\n/* harmony import */ var _modules_Http_Ajax__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../../modules/Http/Ajax */ \"./js/src/react/modules/Http/Ajax.js\");\n/* harmony import */ var _Preloader_Preloader__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../Preloader/Preloader */ \"./js/src/react/components/Preloader/Preloader.js\");\n/* harmony import */ var _NoticeBanner_NoticeBanner__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../NoticeBanner/NoticeBanner */ \"./js/src/react/components/TabContent/NoticeBanner/NoticeBanner.js\");\n/* harmony import */ var _ScannerInfo_ScannerInfo__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./ScannerInfo/ScannerInfo */ \"./js/src/react/components/TabContent/TabScanner/ScannerInfo/ScannerInfo.js\");\nfunction _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }\nfunction _nonIterableSpread() { throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _iterableToArray(r) { if (\"undefined\" != typeof Symbol && null != r[Symbol.iterator] || null != r[\"@@iterator\"]) return Array.from(r); }\nfunction _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }\nfunction _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = \"function\" == typeof Symbol ? Symbol : {}, n = r.iterator || \"@@iterator\", o = r.toStringTag || \"@@toStringTag\"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, \"_invoke\", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError(\"Generator is already running\"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = \"next\"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError(\"iterator result is not an object\"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i[\"return\"]) && t.call(i), c < 2 && (u = TypeError(\"The iterator does not provide a '\" + o + \"' method\"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, \"GeneratorFunction\")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, \"constructor\", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, \"constructor\", GeneratorFunction), GeneratorFunction.displayName = \"GeneratorFunction\", _regeneratorDefine2(GeneratorFunctionPrototype, o, \"GeneratorFunction\"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, \"Generator\"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, \"toString\", function () { return \"[object Generator]\"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }\nfunction _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, \"\", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o(\"next\", 0), o(\"throw\", 1), o(\"return\", 2)); }, _regeneratorDefine2(e, r, n, t); }\nfunction asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }\nfunction _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, \"next\", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, \"throw\", n); } _next(void 0); }); }; }\nfunction _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"); }\nfunction _unsupportedIterableToArray(r, a) { if (r) { if (\"string\" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return \"Object\" === t && r.constructor && (t = r.constructor.name), \"Map\" === t || \"Set\" === t ? Array.from(r) : \"Arguments\" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }\nfunction _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }\nfunction _iterableToArrayLimit(r, l) { var t = null == r ? null : \"undefined\" != typeof Symbol && r[Symbol.iterator] || r[\"@@iterator\"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t[\"return\"] && (u = t[\"return\"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }\nfunction _arrayWithHoles(r) { if (Array.isArray(r)) return r; }\n\n\n\n\n\n\n\nfunction TabScanner(_ref) {\n var sectionId = _ref.sectionId,\n sectionData = _ref.sectionData,\n isActive = _ref.isActive;\n var _useState = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null),\n _useState2 = _slicedToArray(_useState, 2),\n scannerStatus = _useState2[0],\n setScannerStatus = _useState2[1];\n var prevStatusRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);\n var _useState3 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null),\n _useState4 = _slicedToArray(_useState3, 2),\n currentStage = _useState4[0],\n setCurrentStage = _useState4[1];\n var _useState5 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]),\n _useState6 = _slicedToArray(_useState5, 2),\n completedStages = _useState6[0],\n setCompletedStages = _useState6[1];\n // Progress (0-100%) of the currently running stage only, plus the raw offset/total it was\n // computed from. `null` percent means the backend couldn't determine a total for this stage\n // (e.g. quick stages like clean_results), so the UI shows an indeterminate progress state.\n var _useState7 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({\n offset: null,\n total: null,\n percent: null\n }),\n _useState8 = _slicedToArray(_useState7, 2),\n currentStageProgress = _useState8[0],\n setCurrentStageProgress = _useState8[1];\n var intervalRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);\n // Tracks whether the previous poll tick saw the scanner running, so we know to do one last\n // log refresh right after it finishes (the last tick's fetch may have missed the very last\n // rows written while the \"send_results\"/cure stages were wrapping up).\n var scanWasInProgressRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);\n var errorShownRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(false);\n\n // Pulls the freshest scan log rows (written server-side to the DB by the background scan,\n // see CleantalkSP\\SpbctWP\\Scanner\\ScanningLog\\Repository::write()) and renders them into the\n // live \".spbc_log-wrapper\" block. This reuses the same AJAX action the historical, paginated\n // \"#spbcscan-results-log-module\" log uses to lazy-load older rows on scroll; here it's always\n // called with offset 0 to get the latest rows (Repository::getAll() orders by timestamp DESC).\n var fetchScanLog = function fetchScanLog() {\n jQuery.ajax({\n url: spbcSettings.ajaxurl,\n type: 'POST',\n data: {\n action: 'spbc_scanner_load_more_scan_logs',\n security: spbcSettings.ajax_nonce,\n no_cache: Math.random(),\n offset: 0\n },\n success: function success(data) {\n if (!data || !data.success || !data.data) {\n return;\n }\n if (!jQuery('.spbc_log-wrapper .panel-body').length) {\n jQuery('.spbc_log-wrapper').append('

');\n }\n jQuery('.spbc-scan-log-title').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper').removeClass('spbc---hidden');\n jQuery('#spbc_log_actions_wrapper').removeClass('spbc---hidden');\n jQuery('.spbc_log-wrapper .panel-body').html(data.data);\n }\n });\n };\n var fetchScannerStatus = function fetchScannerStatus() {\n var data = {\n action: 'spbc_scanner__getScannerStatus'\n };\n var params = {\n callback: scannerStatusCallback,\n errorOutput: function errorOutput(msg) {\n console.log(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n // (Re)starts the polling interval. Used on mount and whenever a new scan is kicked off,\n // so polling reliably resumes even if it had been stopped after a previous scan ended.\n var startPolling = function startPolling() {\n clearInterval(intervalRef.current);\n errorShownRef.current = false;\n\n // First request do immediately\n fetchScannerStatus();\n\n // Start polling to fetch the scanner state\n intervalRef.current = setInterval(fetchScannerStatus, 5000);\n };\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n // Stop polling on the scanner ended or failed\n if (scannerStatus === 'end' || scannerStatus === 'error') {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n }\n // Reload page during status changed in_progress -> end\n if (prevStatusRef.current === 'in_progress' && scannerStatus === 'end') {\n window.location.reload();\n }\n prevStatusRef.current = scannerStatus;\n }, [scannerStatus]);\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n window.spbcScanner = window.spbcScanner || {};\n window.spbcScanner.active = false;\n startPolling();\n // Cleanup on unmount\n return function () {\n return clearInterval(intervalRef.current);\n };\n }, []);\n function scannerStatusCallback(result) {\n var _result$data;\n if (result && result !== null && result !== void 0 && (_result$data = result.data) !== null && _result$data !== void 0 && _result$data.status) {\n var _result$data2, _result$data3, _result$data$current_, _result$data4, _result$data$current_2, _result$data5, _result$data$current_3, _result$data6;\n var status = result.data.status;\n if (status === 'error') {\n clearInterval(intervalRef.current);\n intervalRef.current = null;\n setScannerStatus('error');\n window.spbcScanner = window.spbcScanner || {};\n window.spbcScanner.active = false;\n scanWasInProgressRef.current = false;\n if (!errorShownRef.current) {\n errorShownRef.current = true;\n var errorMsg = result.data.error || 'Internal server error.';\n var text = result.data.stage ? errorMsg + '
Stage: ' + result.data.stage : errorMsg;\n if (typeof spbcModal !== 'undefined' && spbcModal.open) {\n spbcModal.open().putError(text);\n }\n }\n return;\n }\n setScannerStatus(status);\n setCurrentStage((result === null || result === void 0 || (_result$data2 = result.data) === null || _result$data2 === void 0 || (_result$data2 = _result$data2.current_stage) === null || _result$data2 === void 0 ? void 0 : _result$data2.stage) || null);\n setCompletedStages(((result === null || result === void 0 || (_result$data3 = result.data) === null || _result$data3 === void 0 ? void 0 : _result$data3.completed_stages) || []).map(function (stage) {\n return stage.stage;\n }));\n setCurrentStageProgress({\n offset: (_result$data$current_ = result === null || result === void 0 || (_result$data4 = result.data) === null || _result$data4 === void 0 || (_result$data4 = _result$data4.current_stage) === null || _result$data4 === void 0 ? void 0 : _result$data4.offset) !== null && _result$data$current_ !== void 0 ? _result$data$current_ : null,\n total: (_result$data$current_2 = result === null || result === void 0 || (_result$data5 = result.data) === null || _result$data5 === void 0 || (_result$data5 = _result$data5.current_stage) === null || _result$data5 === void 0 ? void 0 : _result$data5.total) !== null && _result$data$current_2 !== void 0 ? _result$data$current_2 : null,\n percent: (_result$data$current_3 = result === null || result === void 0 || (_result$data6 = result.data) === null || _result$data6 === void 0 || (_result$data6 = _result$data6.current_stage) === null || _result$data6 === void 0 ? void 0 : _result$data6.percent) !== null && _result$data$current_3 !== void 0 ? _result$data$current_3 : null\n });\n if (status === 'in_progress') {\n scanWasInProgressRef.current = true;\n window.spbcScanner.active = true;\n fetchScanLog();\n } else if (scanWasInProgressRef.current) {\n // Scan just finished: refresh once more to catch the last rows written\n // while the scan was wrapping up (e.g. the \"send_results\" stage duration entry).\n scanWasInProgressRef.current = false;\n window.spbcScanner.active = false;\n fetchScanLog();\n }\n } else {\n // Stop polling if the wrong answer coming\n clearInterval(intervalRef.current);\n setScannerStatus(null);\n setCurrentStage(null);\n setCompletedStages([]);\n setCurrentStageProgress({\n offset: null,\n total: null,\n percent: null\n });\n }\n }\n\n /** **************** SUPPORT TEMPORARY BOTH CONTENT FETCHED - LEGACY AND NEW *****************/\n var _useState9 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)({\n sectionData: sectionData\n }),\n _useState0 = _slicedToArray(_useState9, 2),\n sectionDataLegacy = _useState0[0],\n setSectionDataLegacy = _useState0[1];\n var _useState1 = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false),\n _useState10 = _slicedToArray(_useState1, 2),\n scannerDataLegacyFetched = _useState10[0],\n setSectionDataLegacyFetched = _useState10[1];\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {\n fetchOldContent();\n }, []);\n var fetchOldContent = /*#__PURE__*/function () {\n var _ref2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {\n var requestData, data, _t;\n return _regenerator().w(function (_context) {\n while (1) switch (_context.p = _context.n) {\n case 0:\n _context.p = 0;\n requestData = {\n action: 'spbc_settings__draw_elements',\n tab_name: sectionId\n };\n _context.n = 1;\n return (0,_modules_Http_Ajax__WEBPACK_IMPORTED_MODULE_3__.sendAjaxRequest)(requestData);\n case 1:\n data = _context.v;\n setSectionDataLegacy(data);\n setSectionDataLegacyFetched(true);\n document.dispatchEvent(new CustomEvent('spbctTabLoaded', {\n bubbles: true,\n detail: {\n tab: sectionId\n }\n }));\n _context.n = 3;\n break;\n case 2:\n _context.p = 2;\n _t = _context.v;\n console.error('Error fetching content:', _t);\n setSectionDataLegacy('');\n setSectionDataLegacyFetched(true);\n case 3:\n return _context.a(2);\n }\n }, _callee, null, [[0, 2]]);\n }));\n return function fetchOldContent() {\n return _ref2.apply(this, arguments);\n };\n }();\n if (!scannerDataLegacyFetched) {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_Preloader_Preloader__WEBPACK_IMPORTED_MODULE_4__[\"default\"], null);\n }\n /** **************** END SUPPORT TEMPORARY BOTH CONTENT FETCHED *****************/\n\n /** **** L E G A C Y ******/\n\n /**\n * Show popup tip\n *\n * @param {object} showIcon\n */\n window.spbcPopupTipShow = function (showIcon) {\n // eslint-disable-line no-unused-vars\n var tipTitle = showIcon.attr('spbc_tip_title') || null;\n var tipText = showIcon.attr('spbc_tip_text') || null;\n var _removeDescFunc = function removeDescFunc(e) {\n if (typeof e === 'undefined' || jQuery(e.target).parent('.spbc_popup_tip--wrapper').length == 0 || jQuery(e.target).hasClass('spbc_popup_tip--icon---cancel')) {\n jQuery('.spbc_popup_tip--wrapper').hide();\n jQuery(document).off('click', _removeDescFunc);\n }\n };\n _removeDescFunc();\n showIcon.after('
');\n var obj = jQuery('.spbc_popup_tip--wrapper');\n obj.empty().append('
').css({\n top: showIcon.position().top - 2,\n left: showIcon.position().left + 25\n });\n obj.append('').append('

' + tipTitle + '

').append('

' + tipText + '

');\n jQuery(document).on('click', _removeDescFunc);\n showIcon.parents('tr').on('mouseout', _removeDescFunc);\n };\n\n /**\n * View file in the scanner\n *\n * @param {object} obj\n */\n window.spbcScannerButtonFileViewEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_file_view',\n file_id: self.parents('.row-actions').attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n document.addEventListener('spbcModalClosed', function (e) {\n spbcReloadAccordion();\n });\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Delete file from analysis log\n *\n * @param {object} obj\n */\n window.spbcScannerAnalysisLogDeleteFromLog = function (obj) {\n // eslint-disable-line no-unused-vars\n var fileIds = [];\n var self = jQuery(obj);\n var spinner;\n if (jQuery(obj).hasClass('tbl-row_action')) {\n fileIds.push(self.parents('.row-actions').attr('uid'));\n spinner = self.parent().siblings('.tbl-preloader--tiny');\n } else {\n var selectedItems = self.closest('#spbc_tbl__scanner_analysis_log').find('.cb-select:checked');\n if (selectedItems.length === 0) {\n alert('Please, select elements.');\n return;\n }\n selectedItems.each(function (index, element) {\n var elementId = jQuery(element).val();\n fileIds.push(elementId);\n });\n spinner = self.children('.tbl-preloader--small');\n }\n var data = {\n action: 'spbc_scanner_analysis_log_delete_from_log',\n file_ids: fileIds\n };\n var params = {\n spinner: spinner,\n callback: function callback() {\n window.location.reload();\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * View page in the scanner\n *\n * @param {object} obj\n */\n window.spbcScannerButtonPageViewEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_page_view',\n page_url: self.parents('.row-actions').attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n }\n };\n var wpWrap = jQuery('#wpwrap');\n var dialogWindow = jQuery('#spbc_dialog');\n dialogWindow.dialog({\n modal: true,\n title: 'Loading..',\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(wpWrap.width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: false,\n resizable: false,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = 'auto';\n jQuery('#spbc_dialog').height(document.documentElement.clientHeight / 100 * 25);\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n }\n });\n dialogWindow.append('\"Wait');\n var spinner = jQuery('#spbc_file_view_preloader');\n var sizeMultiplier = wpWrap.width() * 0.0004;\n spinner.height(128 * sizeMultiplier);\n spinner.width(128 * sizeMultiplier);\n spinner.css({\n left: dialogWindow.width() / 2 - 128 * sizeMultiplier / 2\n });\n spinner.css({\n top: dialogWindow.height() / 2 - 128 * sizeMultiplier / 2\n });\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Callback for file view\n *\n * @param {object} result\n */\n function spbcScannerButtonViewCallback(result) {\n // eslint-disable-line no-unused-vars\n var dialogWindow = jQuery('#spbc_dialog');\n dialogWindow.empty();\n jQuery('#spbc_file_view_preloader').css({\n display: 'none'\n });\n var rowTemplate = '
%s';\n rowTemplate += '

%s


';\n\n /* eslint-disable guard-for-in */\n for (var row in result.file) {\n dialogWindow.append(rowTemplate.printf(row, result.file[row]));\n }\n var contentHeight = Object.keys(result.file).length * 19 < 76 ? 76 : Object.keys(result.file).length * 19;\n var visibleHeight = document.documentElement.clientHeight / 100 * 75;\n var overflow = contentHeight < visibleHeight ? 'hidden' : 'scroll';\n var height = overflow === 'scroll' ? visibleHeight : contentHeight;\n dialogWindow.css({\n height: height,\n overflow: overflow\n });\n dialogWindow.dialog({\n modal: true,\n title: 'Loaded: ' + result.file_path,\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(jQuery('#wpwrap').width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: true,\n resizable: false,\n height: height,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = overflow;\n jQuery('#spbc_dialog').height(height);\n jQuery('.ui-widget-overlay').on('click', function () {\n jQuery('#spbc_dialog').dialog('close');\n });\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n }\n });\n console.log('FILE_VIEWED');\n }\n\n /**\n * Event for file view bad\n *\n * @param {object} obj\n */\n window.spbcScannerButtonFileViewBadEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_file_view',\n file_id: self.parents('.row-actions').attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewBadFileCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Event for page view bad\n *\n * @param {object} obj\n */\n window.spbcScannerButtonPageViewBadEvent = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var data = {\n action: 'spbc_scanner_page_view',\n page_url: self.parent().attr('uid')\n };\n var params = {\n spinner: self.parent().siblings('.tbl-preloader--tiny'),\n callback: spbcScannerButtonViewBadCallback,\n errorOutput: function errorOutput(msg) {\n spbcModal.open().putError(msg);\n }\n };\n spbcSendAJAXRequest(data, params);\n };\n\n /**\n * Callback for file view bad\n *\n * @param {object} result\n * @param {object} data\n * @param {object} params\n */\n function spbcScannerButtonViewBadFileCallback(result, data, params) {\n // eslint-disable-line no-unused-vars\n console.log('FILE_VIEWED_BAD_CODE_FILE');\n jQuery('#spbc_dialog').empty();\n var rowTemplate = '
%s';\n rowTemplate += '

%s


';\n var rowTemplateBad = '
';\n rowTemplateBad += '%s';\n rowTemplateBad += '

%s


';\n var severityParts = [];\n var severityPartsSorted = [];\n var severityKeysSorted = [];\n result.weak_spots = JSON.parse(result.weak_spots);\n /* eslint-disable guard-for-in */\n for (var severity in result.weak_spots) {\n severityParts[severity] = Object.keys(result.weak_spots[severity]);\n }\n severityKeysSorted = Object.keys(severityParts).sort();\n\n /* eslint-disable guard-for-in */\n for (var key in severityKeysSorted) {\n severityPartsSorted[severityKeysSorted[key]] = severityParts[severityKeysSorted[key]];\n }\n\n /* eslint-disable guard-for-in */\n for (var severityPart in severityPartsSorted) {\n var headerName = 'spbc-dialog-row-header-' + severityPart.toLowerCase();\n var headerHtml = '';\n headerHtml += severityPart + ' weakspots:';\n var dialog = jQuery('#spbc_dialog');\n dialog.append(rowTemplate.printf('', headerHtml));\n dialog.append(rowTemplate.printf('...', '...'));\n var header = dialog.find('#' + headerName);\n header.parent().parent().css('text-align', 'center');\n header.parent().parent().css('border', '1px dotted');\n header.parent().parent().css('background', 'darkgrey');\n\n /* eslint-disable guard-for-in */\n for (var strNum in severityParts[severityPart]) {\n // Sort bad line\n strNum = _toConsumableArray(new Set(strNum)).sort();\n var row = +severityParts[severityPart][strNum];\n var displayDepth = 2;\n var temp = void 0;\n // Display only existing lines of file\n for (var currRow = row - displayDepth; currRow <= row + displayDepth; currRow++) {\n if (typeof result.file[currRow] !== 'undefined') {\n temp = row === currRow ? rowTemplateBad : rowTemplate;\n dialog.append(temp.printf(currRow, result.file[currRow]));\n }\n }\n\n // Placing an empty string between results\n dialog.append(rowTemplate.printf('...', '...'));\n }\n }\n\n // Removing last empty string\n jQuery('#spbc_dialog div').last().remove();\n var contentHeight = jQuery('#spbc_dialog div').length * 19 < 76 ? 76 : jQuery('#spbc_dialog div').length * 19;\n var visibleHeight = document.documentElement.clientHeight / 100 * 75;\n var overflow = contentHeight < visibleHeight ? 'no_scroll' : 'scroll';\n var height = overflow === 'scroll' ? visibleHeight : contentHeight;\n jQuery('#spbc_dialog').dialog({\n modal: true,\n title: result.file_path,\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(jQuery('#wpwrap').width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: true,\n resizable: false,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = overflow;\n jQuery('#spbc_dialog').height(height);\n jQuery('.ui-widget-overlay').on('click', function () {\n jQuery('#spbc_dialog').dialog('close');\n });\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n }\n });\n }\n\n /**\n * Callback for file view bad\n *\n * @param {object} result\n * @param {object} data\n * @param {object} params\n */\n function spbcScannerButtonViewBadCallback(result, data, params) {\n // eslint-disable-line no-unused-vars\n console.log('FILE_VIEWED_BAD_CODE');\n var afterClose = function afterClose() {};\n if (result.success == true) {\n console.log(result);\n var rowTemplate = '
' + '%s' + '

%s

' + '
';\n var rowTemplateBad = '
' + '%s' + '

%s

' + '
';\n var strNums = [];\n result.weak_spots = JSON.parse(result.weak_spots);\n /* eslint-disable guard-for-in */\n for (var severity in result.weak_spots) {\n strNums = strNums.concat(Object.keys(result.weak_spots[severity]));\n }\n strNums = _toConsumableArray(new Set(strNums)).sort(); // Sort bad line\n // get a map of bad strings\n // TODO: Probably there can get something else except CRITICAL in the future\n var badStringsMap = new Map(Object.entries(result.weak_spots.CRITICAL));\n /* eslint-disable guard-for-in */\n for (var strNum in strNums) {\n if (strNum == 0 && badStringsMap.get(strNums[strNum]).toLowerCase().includes('doctype')) {\n continue;\n }\n var row = +strNums[strNum];\n var displayDepth = 2;\n\n // Display only existing lines of file\n for (var currRow = row - displayDepth; currRow <= row + displayDepth; currRow++) {\n if (typeof result.file[currRow] !== 'undefined') {\n var isBadString = row === currRow;\n var unformattedRow = void 0;\n var highlitedRow = void 0;\n if (isBadString) {\n unformattedRow = rowTemplateBad;\n } else {\n unformattedRow = rowTemplate;\n }\n if (isBadString && !result.file[currRow].includes('iframe')) {\n highlitedRow = unformattedRow.printf(currRow, badStringsMap.get(strNums[strNum]));\n } else {\n highlitedRow = unformattedRow.printf(currRow, result.file[currRow]);\n }\n jQuery('#spbc_dialog').append(highlitedRow);\n }\n }\n\n // Placing an empty string between results\n jQuery('#spbc_dialog').append(rowTemplate.printf('', ''));\n\n // Removing last empty string\n jQuery('#spbc_dialog div').last().remove();\n }\n } else {\n var wrapperElement = '
%s
';\n jQuery('#spbc_dialog').append(wrapperElement.printf(result.content));\n afterClose = function afterClose() {\n document.location.reload();\n };\n }\n var contentHeight = jQuery('#spbc_dialog div').length * 19 < 76 ? 76 : jQuery('#spbc_dialog div').length * 19;\n var visibleHeight = document.documentElement.clientHeight / 100 * 75;\n var height = 'auto';\n var overflow = contentHeight < visibleHeight ? 'no_scroll' : 'scroll';\n jQuery('#spbc_dialog').dialog({\n modal: true,\n title: result.file_path,\n position: {\n my: 'center top',\n at: 'center top+100px',\n of: window\n },\n width: +(jQuery('#wpwrap').width() / 100 * 80),\n show: {\n effect: 'blind',\n duration: 500\n },\n draggable: true,\n resizable: false,\n closeText: 'X',\n classes: {\n 'ui-dialog': 'spbc---top'\n },\n open: function open(event, ui) {\n event.target.style.overflow = overflow;\n jQuery('#spbc_dialog').height(height);\n jQuery('.ui-widget-overlay').on('click', function () {\n jQuery('#spbc_dialog').dialog('close');\n });\n },\n beforeClose: function beforeClose(event, ui) {\n document.body.style.overflow = 'auto';\n jQuery('#spbc_dialog').empty();\n },\n close: afterClose\n });\n }\n\n /**\n * Cure file\n * @param {object} obj\n */\n window.spbcScannerButtonCureFileAjaxHandler = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var fileFastHash = self.parents('.row-actions').attr('uid');\n var data = {\n action: 'spbc_file_cure_ajax_action',\n file_fast_hash: fileFastHash,\n security: spbcSettings.ajax_nonce\n };\n jQuery.ajax({\n type: 'POST',\n async: true,\n url: spbcSettings.ajaxurl,\n data: data,\n success: function success(result) {\n if (result.success) {\n spbcModal.open().put(result.data);\n document.addEventListener('spbcModalClosed', function (e) {\n document.location.reload();\n });\n } else {\n spbcModal.open().putError(result.data);\n }\n }\n });\n };\n\n /**\n * Restore file from backup\n * @param {object} obj\n */\n window.spbcScannerButtonRestoreFromBackupAjaxHandler = function (obj) {\n // eslint-disable-line no-unused-vars\n var self = jQuery(obj);\n var fileFastHash = self.parents('.row-actions').attr('uid');\n var data = {\n action: 'spbc_restore_file_from_backup_ajax_action',\n file_fast_hash: fileFastHash,\n security: spbcSettings.ajax_nonce\n };\n jQuery.ajax({\n type: 'POST',\n async: true,\n url: spbcSettings.ajaxurl,\n data: data,\n success: function success(result) {\n if (result.success) {\n spbcModal.open().put(result.data);\n } else {\n spbcModal.open().putError(result.data);\n }\n document.addEventListener('spbcModalClosed', function (e) {\n document.location.reload();\n });\n }\n });\n };\n\n /**\n * Show copy log to clipboard hint\n * @param {string} text\n */\n function spbcScannerCopyLogToClipboardHint(text) {\n jQuery('#spbc_scanner_copy_log_to_clipboard_hint').text(text);\n jQuery('#spbc_scanner_copy_log_to_clipboard_hint').show();\n jQuery('#spbc_scanner_copy_log_to_clipboard_hint').animate({\n opacity: 0\n }, 3000, function () {\n jQuery(this).hide();\n jQuery(this).css('opacity', 1);\n });\n }\n jQuery(document).ready(function () {\n // EVENT HADLING\n spbcTblBulkActionsListen(); // Table. Row bulk handler\n spbcTblRowActionsListen(); // Table. Row actions handler\n spbcTblPaginationListen(); // Table. Pagination handler\n spbcTblSortListen(); // Table. Sort handler\n\n spbcStartShowHide();\n jQuery(document).on('click', '.spbc_popup_tip--icon---show', function () {\n spbcPopupTipShow(jQuery(this));\n });\n jQuery('#spbc_perform_scan').on('click', function () {\n jQuery('#spbcscan-results-log-module').remove();\n // spbcScanner.actionControl();\n\n if (!jQuery('.spbc_log-wrapper .panel-body').length) {\n jQuery('.spbc_log-wrapper').append('
');\n }\n jQuery('.spbc_log-wrapper').resizable({\n handles: 'n, s',\n containment: 'document'\n });\n });\n jQuery('#spbcscan-results-log-module').resizable({\n handles: 'n, s',\n containment: 'document'\n });\n logActionsHandleClearLogs();\n logActionsHandleMoreLogs();\n logActionsHandleCopyToClipboard();\n });\n function logActionsHandleClearLogs() {\n // Clear scanner's tables\n jQuery(document).on('click', '#spbc_scanner_clear', function () {\n // eslint-disable-line no-unused-vars\n if (!confirm(spbcSettings.clear_results_confirm)) {\n return;\n }\n spbcSendAJAXRequest({\n action: 'spbc_scanner_clear'\n }, {\n type: 'GET',\n successCallback: function successCallback() {\n document.location = document.location;\n },\n complete: null,\n errorOutput: null,\n context: this,\n timeout: 40000,\n success: function success() {\n document.location.reload();\n }\n });\n });\n }\n function logActionsHandleCopyToClipboard() {\n if (jQuery('#spbc_scanner_copy_log_to_clipboard').length) {\n jQuery('#spbc_scanner_copy_log_to_clipboard').on('click', function (e) {\n e.preventDefault();\n if (navigator.clipboard) {\n var log = jQuery('#spbcscan-results-log-module .panel-body').html();\n var formattedLog = log.replace(//g, '').replace(/<\\/b>/g, '');\n formattedLog = formattedLog.replace(/

/g, '\\n').replace(/<\\/p>/g, '');\n navigator.clipboard.writeText(formattedLog).then(function () {\n spbcScannerCopyLogToClipboardHint(spbcScaner.copy_log_to_clipboard_hint);\n })[\"catch\"](function (err) {\n spbcScannerCopyLogToClipboardHint(spbcScaner.copy_log_to_clipboard_hint_failed);\n });\n } else {\n spbcScannerCopyLogToClipboardHint(spbcScaner.copy_log_to_clipboard_hint_unsupported);\n }\n });\n }\n }\n function logActionsHandleMoreLogs() {\n var element = jQuery('#spbcscan_results_log_body');\n var logAppendPreloader = jQuery('#spbcscan_results_log_preloader');\n if (!element || !logAppendPreloader) {\n return;\n }\n element.on('scroll', function () {\n // Check if load completed\n if (jQuery(this).data('stop-load')) {\n return;\n }\n\n // Is end of scroll\n var isAtBottom = this.scrollHeight - this.scrollTop <= this.clientHeight + 1;\n if (isAtBottom) {\n var $this = jQuery(this);\n var currentOffset = parseInt($this.data('offset')) || 0;\n logAppendPreloader.css('visibility', 'visible');\n jQuery.ajax({\n url: spbcSettings.ajaxurl,\n type: 'POST',\n data: {\n action: 'spbc_scanner_load_more_scan_logs',\n security: spbcSettings.ajax_nonce,\n no_cache: Math.random(),\n offset: currentOffset\n },\n xhrFields: {\n responseType: 'text'\n },\n success: function success(data) {\n if (data.success && data.data) {\n // increase and set new offset\n var newOffset = currentOffset + 100;\n $this.data('offset', newOffset);\n\n // add html to the log\n jQuery('#spbcscan_results_log_body').append(data.data);\n\n // if no data - stop load\n if (data.data.length === 0 || data.data.trim() === '') {\n $this.data('stop-load', 1);\n }\n } else {\n $this.data('stop-load', 1);\n }\n logAppendPreloader.css('visibility', 'hidden');\n },\n error: function error(xhr, status, _error) {\n logAppendPreloader.css('visibility', 'hidden');\n $this.data('stop-load', 1);\n }\n });\n }\n });\n }\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", {\n className: \"spbc_wrapper_field\"\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(\"div\", {\n className: 'spbc_tab_fields_group'\n }, window.spbcSettings && window.spbcSettings.critical > 0 && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_NoticeBanner_NoticeBanner__WEBPACK_IMPORTED_MODULE_5__[\"default\"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_ScannerInfo_ScannerInfo__WEBPACK_IMPORTED_MODULE_6__[\"default\"], {\n sectionData: sectionData,\n scannerStatus: scannerStatus,\n currentStage: currentStage,\n completedStages: completedStages,\n currentStageProgress: currentStageProgress,\n onScanStarted: startPolling\n }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_TabContentTypes_Html_TabContentHtml__WEBPACK_IMPORTED_MODULE_2__[\"default\"], {\n sectionId: sectionId,\n sectionData: sectionDataLegacy,\n isActive: isActive\n })));\n}\n\n//# sourceURL=webpack:///./js/src/react/components/TabContent/TabScanner/TabScanner.js?\n}"); /***/ }), diff --git a/js/spbc-scanner-plugin.min.js b/js/spbc-scanner-plugin.min.js index 60196802e..139d68906 100644 --- a/js/spbc-scanner-plugin.min.js +++ b/js/spbc-scanner-plugin.min.js @@ -1,2 +1,2 @@ -class SpbcMalwareScanner{first_start=!0;active=!1;root="";settings=[];states=["get_cms_hashes","get_modules_hashes","clean_results","file_system_analysis","get_approved_hashes","get_denied_hashes","signature_analysis","heuristic_analysis","schedule_send_heuristic_suspicious_files","auto_cure_backup","auto_cure","os_cron_analysis","db_trigger_analysis","binary_analysis","outbound_links","frontend_analysis","important_files_listing","send_results"];state=null;offset=0;amount=0;amount_coefficient=1;total_scanned=0;scan_percent=0;percent_completed=0;paused=!1;button=null;spinner=null;progress_overall=null;progressbar=null;progressbar_text=null;timeout=6e4;state_timer=0;constructor(t){for(var e in jQuery("#spbcscan-results-log-module").length&&jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),void 0!==t.settings.auto_cure&&(t.settings.scanner__auto_cure_backup="1"),t)void 0!==this[e]&&(this[e]=t[e])}actionControl(){null===this.state?this.start():this.paused?(this.resume(),this.controller()):this.pause()}start(){this.active=!0,this.state_timer=Math.round((new Date).getTime()/1e3),this.state=this.getNextState(null),this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold"),this.progressbar.show(500),this.progress_overall.show(500),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),setTimeout(()=>{this.controller()},1e3)}pause(t,e,s){console.log("PAUSE"),this.button.html(spbcScaner.button_scan_resume),this.spinner.css({display:"none"}),this.paused=!0,this.active=!1}resume(t){console.log("RESUME"),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),this.paused=!1,this.active=!0}end(t){this.progressbar.hide(500),this.progress_overall.hide(500),this.button.html(spbcScaner.button_scan_perform),this.spinner.css({display:"none"}),this.state=null,this.plug=!1,this.total_scanned=0,this.active=!1,t?document.location=document.location:spbcSendAJAXRequest({action:"spbc_scanner_tab__reload_accordion"},{notJson:!0,callback:function(t,e,s,a){jQuery(a).accordion("destroy").html(t).accordion({header:"h3",heightStyle:"content",collapsible:!0,active:!1}),spbcTblBulkActionsListen(),spbcTblRowActionsListen(),spbcTblPaginationListen(),spbcTblSortListen(),spbcStartShowHide()}},jQuery("#spbc_scan_accordion"))}controller(t){if(console.log(this.state),void 0!==t&&t.end){if(this.state=this.getNextState(this.state),void 0===this.state)return void this.end();this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold")}if(!0!==this.paused){var e={action:"spbc_scanner_controller_front",method:this.state,offset:this.offset},t={type:"GET",success:this.success,callback:this.successCallback,error:this.error,errorOutput:this.errorOutput,complete:null,context:this,timeout:12e4};switch(this.state){case"get_modules_hashes":this.amount=2;break;case"clear_table":this.amount=1e4;break;case"file_system_analysis":this.amount=700;break;case"auto_cure":this.amount=5;break;case"outbound_links":this.amount=10;break;case"frontend_analysis":this.amount=spbcSettings.frontendAnalysisAmount;break;case"signature_analysis":this.amount=10,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"heuristic_analysis":case"binary_analysis":this.amount=4,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"schedule_send_heuristic_suspicious_files":this.amount=1}e.amount=Math.round(this.amount*this.amount_coefficient),spbcSendAJAXRequest(e,t,jQuery("#spbc_scan_accordion"))}}setCoefficients(t){let e=this.amount_coefficient;"file_system_analysis"===t&&(e*=1.5),this.amount_coefficient=e}getNextState(t){return t=null===t?this.states[0]:this.states[this.states.indexOf(t)+1],t=void 0!==this.settings["scanner__"+t]&&0==+this.settings["scanner__"+t]?this.getNextState(t):t}setPercents(t){this.percent_completed=Math.floor(100*t)/100,this.progressbar.progressbar("option","value",this.percent_completed),this.progressbar_text.text(spbcScaner["progressbar_"+this.state]+" - "+this.percent_completed+"%")}success(t){t.error?this.error({status:200,responseText:t.error},t.error,t.msg):this.successCallback&&this.successCallback(t,this.data,this.obj)}successCallback(e){if(console.log(e),this.interactAccordion(e),void 0!==e.total&&(this.scan_percent=100/e.total),void 0!==e.processed_items&&("heuristic_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Heuristic Analysis

'),"signature_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Signature Analysis

'),this.logFileEntry(e.processed_items)),void 0!==e.stage_data_for_logging&&this.logStageEntry(e.stage_data_for_logging),void 0!==e.cured&&0{this.controller(e)},300)}interactAccordion(t){t.hasOwnProperty("interactivity_data")&&t.interactivity_data.hasOwnProperty("update_text")&&t.interactivity_data.update_text&&t.interactivity_data.hasOwnProperty("refresh_data")&&t.interactivity_data.refresh_data.hasOwnProperty("do_refresh")&&t.interactivity_data.refresh_data.do_refresh&&t.interactivity_data.refresh_data.hasOwnProperty("control_tab")&&t.interactivity_data.refresh_data.control_tab&&spbcReloadAccordion(t.interactivity_data.refresh_data.control_tab,t.interactivity_data.update_text)}error(t,e,s){var a=this.errorOutput;if(console.log("%c APBCT_AJAX_ERROR","color: red;"),console.log(e),console.log(s),console.log(t),"error"==e&&(""==s||"Not found"==s)&&(this.tryCount||(this.tryCount=0,this.retryLimit=30),this.tryCount++,console.log("Try #"+this.tryCount),this.setCoefficients(this.state),this.tryCount<=this.retryLimit))this.pause(),this.resume(),this.controller();else{if(200===t.status)if("parsererror"===e)a("Unexpected response from server. See console for details.",this.state),console.log("%c "+t.responseText,"color: pink;");else{let t=e;void 0!==s&&(t+=" Additional info: "+s),a(t,this.state)}else 500===t.status?a("Internal server error.",this.state):a("Unexpected response code: "+t.status+". Error: "+e,this.state);this.progressbar&&this.progressbar.fadeOut("slow"),this.end()}}errorOutput(t,e){spbcModal.open().putError(t+"
Stage: "+e)}logRaw(t){jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper .panel-body").prepend(t)}logFileEntry(t){for(var e in t)e&&this.logRaw('

'+this.getSiteUTCShiftedTimeString()+" - "+t[e].path+" - "+t[e].module+": "+t[e].status+"

")}logStageEntry(t){void 0!==jQuery(".panel-body .spbc_log-line span").first()&&void 0!==jQuery(".panel-body .spbc_log-line span").first()[0]&&jQuery(".panel-body .spbc_log-line span").first()[0].textContent===t.description||this.logRaw('

test '+this.getSiteUTCShiftedTimeString()+" - "+t.title+" "+t.description+"

")}showLinkForShuffleSalts(t){jQuery("#spbc_notice_about_shuffle_link").remove(),jQuery(jQuery(".spbc_tab--active .spbc_wrapper_field p")[1]).after('")}getSiteUTCShiftedTimeString(){let t=!1;var e=-1*(new Date).getTimezoneOffset()*1e3*60,e=(t="undefined"!=typeof spbcScaner&&void 0!==spbcScaner.timezone_shift&&!1!==spbcScaner.timezone_shift?Date.now()-e+1e3*spbcScaner.timezone_shift:t)?new Date(t):new Date,s=new Intl.DateTimeFormat("en-US",{month:"short"}).format,a=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return s(e)+" "+e.getDate()+" "+e.getFullYear()+" "+e.getHours()+":"+a+":"+i}} +class SpbcMalwareScanner{first_start=!0;active=!1;root="";settings=[];states=["get_cms_hashes","get_modules_hashes","clean_results","file_system_analysis","get_approved_hashes","get_denied_hashes","signature_analysis","heuristic_analysis","schedule_send_heuristic_suspicious_files","auto_cure_backup","auto_cure","os_cron_analysis","db_trigger_analysis","binary_analysis","outbound_links","frontend_analysis","important_files_listing","send_results"];state=null;offset=0;amount=0;amount_coefficient=1;total_scanned=0;scan_percent=0;percent_completed=0;paused=!1;button=null;spinner=null;progress_overall=null;progressbar=null;progressbar_text=null;timeout=6e4;state_timer=0;constructor(t){for(var e in jQuery("#spbcscan-results-log-module").length&&jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),void 0!==t.settings.auto_cure&&(t.settings.scanner__auto_cure_backup="1"),t)void 0!==this[e]&&(this[e]=t[e])}actionControl(){null===this.state?this.start():this.paused?(this.resume(),this.controller()):this.pause()}start(){this.active=!0,this.state_timer=Math.round((new Date).getTime()/1e3),this.state=this.getNextState(null),this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold"),this.progressbar.show(500),this.progress_overall.show(500),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),setTimeout(()=>{this.controller()},1e3)}pause(t,e,s){console.log("PAUSE"),this.button.html(spbcScaner.button_scan_resume),this.spinner.css({display:"none"}),this.paused=!0,this.active=!1}resume(t){console.log("RESUME"),this.button.html(spbcScaner.button_scan_pause),this.spinner.css({display:"inline"}),this.paused=!1,this.active=!0}end(t){this.progressbar.hide(500),this.progress_overall.hide(500),this.button.html(spbcScaner.button_scan_perform),this.spinner.css({display:"none"}),this.state=null,this.plug=!1,this.total_scanned=0,this.active=!1,t?document.location=document.location:spbcSendAJAXRequest({action:"spbc_scanner_tab__reload_accordion"},{notJson:!0,callback:function(t,e,s,a){jQuery(a).accordion("destroy").html(t).accordion({header:"h3",heightStyle:"content",collapsible:!0,active:!1}),spbcTblBulkActionsListen(),spbcTblRowActionsListen(),spbcTblPaginationListen(),spbcTblSortListen(),spbcStartShowHide()}},jQuery("#spbc_scan_accordion"))}controller(t){if(console.log(this.state),void 0!==t&&t.end){if(this.state=this.getNextState(this.state),void 0===this.state)return void this.end();this.setPercents(0),this.scan_percent=0,this.offset=0,this.progress_overall.children("span").removeClass("spbc_bold").filter(".spbc_overall_scan_status_"+this.state).addClass("spbc_bold")}if(!0!==this.paused){var e={action:"spbc_scanner_controller_front",method:this.state,offset:this.offset},t={type:"GET",success:this.success,callback:this.successCallback,error:this.error,errorOutput:this.errorOutput,complete:null,context:this,timeout:12e4};switch(this.state){case"get_modules_hashes":this.amount=2;break;case"clear_table":this.amount=1e4;break;case"file_system_analysis":this.amount=700;break;case"auto_cure":this.amount=5;break;case"outbound_links":this.amount=10;break;case"frontend_analysis":this.amount=spbcSettings.frontendAnalysisAmount;break;case"signature_analysis":this.amount=10,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"heuristic_analysis":case"binary_analysis":this.amount=4,e.status="UNKNOWN,MODIFIED,OK,INFECTED,ERROR";break;case"schedule_send_heuristic_suspicious_files":this.amount=1}e.amount=Math.round(this.amount*this.amount_coefficient),spbcSendAJAXRequest(e,t,jQuery("#spbc_scan_accordion"))}}setCoefficients(t){let e=this.amount_coefficient;"file_system_analysis"===t&&(e*=1.5),this.amount_coefficient=e}getNextState(t){return t=null===t?this.states[0]:this.states[this.states.indexOf(t)+1],t=void 0!==this.settings["scanner__"+t]&&0==+this.settings["scanner__"+t]?this.getNextState(t):t}setPercents(t){this.percent_completed=Math.floor(100*t)/100,this.progressbar.progressbar("option","value",this.percent_completed),this.progressbar_text.text(spbcScaner["progressbar_"+this.state]+" - "+this.percent_completed+"%")}success(t){t.error?this.error({status:200,responseText:t.error},t.error,t.msg):this.successCallback&&this.successCallback(t,this.data,this.obj)}successCallback(e){if(console.log(e),this.interactAccordion(e),void 0!==e.total&&(this.scan_percent=100/e.total),void 0!==e.processed_items&&("heuristic_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Heuristic Analysis

'),"signature_analysis"===this.state&&0!==typeof e.total&&this.logRaw('

Signature Analysis

'),this.logFileEntry(e.processed_items)),void 0!==e.stage_data_for_logging&&this.logStageEntry(e.stage_data_for_logging),void 0!==e.cured&&0{this.controller(e)},300)}interactAccordion(t){t.hasOwnProperty("interactivity_data")&&t.interactivity_data.hasOwnProperty("update_text")&&t.interactivity_data.update_text&&t.interactivity_data.hasOwnProperty("refresh_data")&&t.interactivity_data.refresh_data.hasOwnProperty("do_refresh")&&t.interactivity_data.refresh_data.do_refresh&&t.interactivity_data.refresh_data.hasOwnProperty("control_tab")&&t.interactivity_data.refresh_data.control_tab&&spbcReloadAccordion(t.interactivity_data.refresh_data.control_tab,t.interactivity_data.update_text)}error(t,e,s){var a=this.errorOutput;if(console.log("%c APBCT_AJAX_ERROR","color: red;"),console.log(e),console.log(s),console.log(t),"error"==e&&(""==s||"Not found"==s)&&(this.tryCount||(this.tryCount=0,this.retryLimit=30),this.tryCount++,console.log("Try #"+this.tryCount),this.setCoefficients(this.state),this.tryCount<=this.retryLimit))this.pause(),this.resume(),this.controller();else{if(200===t.status)if("parsererror"===e)a("Unexpected response from server. See console for details.",this.state),console.log("%c "+t.responseText,"color: pink;");else{let t=e;void 0!==s&&(t+=" Additional info: "+s),a(t,this.state)}else 500===t.status?a("Internal server error.",this.state):a("Unexpected response code: "+t.status+". Error: "+e,this.state);this.progressbar&&this.progressbar.fadeOut("slow"),this.end()}}errorOutput(t,e){spbcModal.open().putError(t+"
Stage: "+e)}logRaw(t){jQuery(".spbc-scan-log-title").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper").removeClass("spbc---hidden"),jQuery("#spbc_log_actions_wrapper").removeClass("spbc---hidden"),jQuery(".spbc_log-wrapper .panel-body").prepend(t)}logFileEntry(t){for(var e in t)e&&this.logRaw('

'+this.getSiteUTCShiftedTimeString()+" - "+t[e].path+" - "+t[e].module+": "+t[e].status+"

")}logStageEntry(t){void 0!==jQuery(".panel-body .spbc_log-line span").first()&&void 0!==jQuery(".panel-body .spbc_log-line span").first()[0]&&jQuery(".panel-body .spbc_log-line span").first()[0].textContent===t.description||this.logRaw('

test '+this.getSiteUTCShiftedTimeString()+" - "+t.title+" "+t.description+"

")}showLinkForShuffleSalts(t){jQuery("#spbc_notice_about_shuffle_link").remove(),jQuery(jQuery(".spbc_tab--active .spbc_wrapper_field p")[1]).after('")}getSiteUTCShiftedTimeString(){let t=!1;var e=-1*(new Date).getTimezoneOffset()*1e3*60,e=(t="undefined"!=typeof spbcScaner&&void 0!==spbcScaner.timezone_shift&&!1!==spbcScaner.timezone_shift?Date.now()-e+1e3*spbcScaner.timezone_shift:t)?new Date(t):new Date,s=new Intl.DateTimeFormat("en-US",{month:"short"}).format,a=String(e.getMinutes()).padStart(2,"0"),i=String(e.getSeconds()).padStart(2,"0");return s(e)+" "+e.getDate()+" "+e.getFullYear()+" "+e.getHours()+":"+a+":"+i}} //# sourceMappingURL=spbc-scanner-plugin.min.js.map diff --git a/js/src/react/components/TabContent/TabScanner/ScannerInfo/ScanButton.js b/js/src/react/components/TabContent/TabScanner/ScannerInfo/ScanButton.js index 24a037803..c73dc581a 100644 --- a/js/src/react/components/TabContent/TabScanner/ScannerInfo/ScanButton.js +++ b/js/src/react/components/TabContent/TabScanner/ScannerInfo/ScanButton.js @@ -17,7 +17,7 @@ export default function ScanButton({scannerStatus, scannerError, onScanStarted}) // Once the polling picks up the new scan (or anything else changes the status), the // "starting" state is no longer needed. useEffect(() => { - if (scannerStatus === 'in_progress') { + if (scannerStatus === 'in_progress' || scannerStatus === 'error') { setIsStarting(false); } }, [scannerStatus]); diff --git a/js/src/react/components/TabContent/TabScanner/TabScanner.js b/js/src/react/components/TabContent/TabScanner/TabScanner.js index 4d84f44cb..d2d434ab2 100644 --- a/js/src/react/components/TabContent/TabScanner/TabScanner.js +++ b/js/src/react/components/TabContent/TabScanner/TabScanner.js @@ -20,6 +20,7 @@ export default function TabScanner({sectionId, sectionData, isActive}) { // log refresh right after it finishes (the last tick's fetch may have missed the very last // rows written while the "send_results"/cure stages were wrapping up). const scanWasInProgressRef = useRef(false); + const errorShownRef = useRef(false); // Pulls the freshest scan log rows (written server-side to the DB by the background scan, // see CleantalkSP\SpbctWP\Scanner\ScanningLog\Repository::write()) and renders them into the @@ -47,6 +48,7 @@ export default function TabScanner({sectionId, sectionData, isActive}) { jQuery('.spbc-scan-log-title').removeClass('spbc---hidden'); jQuery('.spbc_log-wrapper').removeClass('spbc---hidden'); + jQuery('#spbc_log_actions_wrapper').removeClass('spbc---hidden'); jQuery('.spbc_log-wrapper .panel-body').html(data.data); }, }); @@ -69,6 +71,7 @@ export default function TabScanner({sectionId, sectionData, isActive}) { // so polling reliably resumes even if it had been stopped after a previous scan ended. const startPolling = () => { clearInterval(intervalRef.current); + errorShownRef.current = false; // First request do immediately fetchScannerStatus(); @@ -77,9 +80,17 @@ export default function TabScanner({sectionId, sectionData, isActive}) { intervalRef.current = setInterval(fetchScannerStatus, 5000); }; + const showScannerError = (message, stage) => { + const errorMsg = message || 'Internal server error.'; + const text = stage ? (errorMsg + '
Stage: ' + stage) : errorMsg; + if (typeof spbcModal !== 'undefined' && spbcModal.open) { + spbcModal.open().putError(text); + } + }; + useEffect(() => { - // Stop polling on the scanner ended - if (scannerStatus === 'end') { + // Stop polling on the scanner ended or failed + if (scannerStatus === 'end' || scannerStatus === 'error') { clearInterval(intervalRef.current); intervalRef.current = null; } @@ -101,6 +112,21 @@ export default function TabScanner({sectionId, sectionData, isActive}) { function scannerStatusCallback(result) { if (result && result?.data?.status) { const status = result.data.status; + + if (status === 'error') { + clearInterval(intervalRef.current); + intervalRef.current = null; + setScannerStatus('error'); + window.spbcScanner = window.spbcScanner || {}; + window.spbcScanner.active = false; + scanWasInProgressRef.current = false; + if ( ! errorShownRef.current ) { + errorShownRef.current = true; + showScannerError(result.data.error, result.data.stage); + } + return; + } + setScannerStatus(status); setCurrentStage(result?.data?.current_stage?.stage || null); setCompletedStages((result?.data?.completed_stages || []).map((stage) => stage.stage)); @@ -758,7 +784,7 @@ export default function TabScanner({sectionId, sectionData, isActive}) { function logActionsHandleClearLogs() { // Clear scanner's tables - jQuery('#spbc_scanner_clear').on('click', function() { // eslint-disable-line no-unused-vars + jQuery(document).on('click', '#spbc_scanner_clear', function() { // eslint-disable-line no-unused-vars if (!confirm(spbcSettings.clear_results_confirm)) { return; } diff --git a/js/src/spbc-scanner-plugin.js b/js/src/spbc-scanner-plugin.js index d34df8b94..1483f6100 100644 --- a/js/src/spbc-scanner-plugin.js +++ b/js/src/spbc-scanner-plugin.js @@ -459,6 +459,7 @@ class SpbcMalwareScanner {/* eslint-disable-line no-unused-vars */ logRaw(messageToLog) { jQuery('.spbc-scan-log-title').removeClass('spbc---hidden'); jQuery('.spbc_log-wrapper').removeClass('spbc---hidden'); + jQuery('#spbc_log_actions_wrapper').removeClass('spbc---hidden'); jQuery('.spbc_log-wrapper .panel-body').prepend( messageToLog ); }; diff --git a/lib/CleantalkSP/Common/Transaction.php b/lib/CleantalkSP/Common/Transaction.php index d891af12b..e4eb2e6ee 100644 --- a/lib/CleantalkSP/Common/Transaction.php +++ b/lib/CleantalkSP/Common/Transaction.php @@ -130,4 +130,13 @@ public function clearTransactionTimer() { $this->setOption($this->start_time_option_name, 0); } + + /** + * Drop the timer and TID so leftover controller/cron ticks cannot continue. + */ + public function reset() + { + $this->clearTransactionTimer(); + $this->saveTID(0); + } } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/Helper.php b/lib/CleantalkSP/SpbctWP/Scanner/Helper.php index b45b8cbcb..6b62c6528 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/Helper.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/Helper.php @@ -268,6 +268,9 @@ public static function clearScannerResults() { global $spbc; + ScannerQueue::abortBackground(); + ScannerQueue::clearRuntimeError(); + $spbc->plugins = array(); $spbc->save('plugins'); diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php index 830435fcb..56277e86c 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScannerQueue.php @@ -169,6 +169,8 @@ public static function launchBackground() return true; } + self::clearRuntimeError(); + $params = array( 'transaction_id' => $transaction_id, 'stage' => 'get_cms_hashes', @@ -197,6 +199,142 @@ public static function launchBackground() ); } + /** + * Stop a running background scan. In-flight PHP may still finish the current + * request, but the next controller/cron tick will not continue. + * + * @return void + */ + public static function abortBackground() + { + Transaction::get('background_scanner')->reset(); + Cron::removeTask('background_scan'); + } + + /** + * Abort the background scan and persist an error for the scanner tab. + * + * @param string $message + * @param string|null $stage + * @return void + */ + public static function failBackground($message, $stage = null) + { + global $spbc; + + self::abortBackground(); + + $existing = get_option('spbc_scanner_runtime_error', array()); + $already_shown = is_array($existing) && ! empty($existing['shown']); + + update_option( + 'spbc_scanner_runtime_error', + array( + 'error' => (string) $message, + 'stage' => (string) $stage, + 'shown' => $already_shown, + ), + false + ); + + if (isset($spbc->data['scanner']) && is_array($spbc->data['scanner'])) { + $spbc->data['scanner']['scan_finished_at'] = time(); + $spbc->save('data'); + } + } + + /** + * @return array{error: string, stage: string}|null + */ + public static function getRuntimeError() + { + $error = get_option('spbc_scanner_runtime_error', array()); + if ( ! is_array($error) || empty($error['error']) || ! empty($error['shown']) ) { + return null; + } + + return array( + 'error' => (string) $error['error'], + 'stage' => isset($error['stage']) ? (string) $error['stage'] : '', + ); + } + + /** + * Remember that the scanner tab already displayed this error. + * + * @return void + */ + public static function markRuntimeErrorShown() + { + $error = get_option('spbc_scanner_runtime_error', array()); + if ( ! is_array($error) || empty($error['error']) ) { + return; + } + + $error['shown'] = true; + update_option('spbc_scanner_runtime_error', $error, false); + } + + /** + * @return void + */ + public static function clearRuntimeError() + { + delete_option('spbc_scanner_runtime_error'); + } + + /** + * Stop the scan if this request fatals inside Surface / ScannerQueue. + * + * @param string|null $stage + * @return void + */ + public static function registerFatalGuard($stage) + { + static $registered = false; + if ($registered) { + return; + } + $registered = true; + + register_shutdown_function(static function () use ($stage) { + self::handleFatalAbort($stage); + }); + } + + /** + * @param string|null $stage + * @return void + */ + public static function handleFatalAbort($stage) + { + $error = error_get_last(); + if ($error === null || empty($error['type']) || empty($error['file'])) { + return; + } + + $fatal_types = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR); + if ( ! in_array($error['type'], $fatal_types, true) ) { + return; + } + + $file = $error['file']; + $in_scanner = ( + strpos($file, DIRECTORY_SEPARATOR . 'Scanner' . DIRECTORY_SEPARATOR) !== false + || strpos($file, 'spbc-scanner.php') !== false + ); + if ( ! $in_scanner ) { + return; + } + + $message = ! empty($error['message']) ? $error['message'] : 'Internal server error.'; + if (stripos($message, 'Maximum execution time') !== false) { + $message = 'Internal server error.'; + } + + self::failBackground($message, $stage); + } + /** * Description here * @@ -223,11 +361,27 @@ public static function controllerBackground($transaction_id = null, $stage = nul return array('error' => 'controllerBackground: UNKNOWN_METHOD: ' . $self->stage); } + self::registerFatalGuard($self->stage); + $result = $self->{$self->stage}(); + // Cleared / aborted while this long stage was running — do not continue the chain. + if ((int) get_option('spbc_transaction__background_scanner_start_time', 0) === 0) { + Cron::removeTask('background_scan'); + Transaction::get('background_scanner')->reset(); + + return true; + } + // Handling errors $spbc->error_toggle(! empty($result['error']), 'cron_scan', $result); if ( ! empty($result['error']) ) { + $error_message = is_array($result) && isset($result['error']) ? $result['error'] : 'Internal server error.'; + if (is_array($error_message)) { + $error_message = isset($error_message['error']) ? $error_message['error'] : 'Internal server error.'; + } + self::failBackground((string) $error_message, $self->stage); + return $result; } diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/ScanningLogFacade.php b/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/ScanningLogFacade.php index f454fc71a..aa6b58d34 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/ScanningLogFacade.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/ScanningLogFacade.php @@ -40,6 +40,14 @@ public static function render(int $limit = 100, int $offset = 0, bool $only_rows return ''; } + /** + * Render copy/clear actions under the scan log. + */ + public static function renderLogActions(): string + { + return Template::renderLogActionsHTML(); + } + /** * Get data for PDF * @psalm-suppress PossiblyUnusedMethod diff --git a/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/Template.php b/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/Template.php index f128f2204..f075a7086 100644 --- a/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/Template.php +++ b/lib/CleantalkSP/SpbctWP/Scanner/ScanningLog/Template.php @@ -3,7 +3,6 @@ namespace CleantalkSP\SpbctWP\Scanner\ScanningLog; use CleantalkSP\Common\TextPlate; -use CleantalkSP\Variables\Server; /** * Template class for scan logs. @@ -27,11 +26,9 @@ public static function render(array $data, int $limit): string {{log_rows}} - {{log_actions}} '; $_plate = [ 'log_rows' => self::generateRows($data), - 'log_actions' => self::renderLogActionsHTML(), 'stop_load' => Repository::getTotalCount() <= $limit ? '1' : '0', ]; @@ -44,12 +41,18 @@ public static function render(array $data, int $limit): string public static function renderLogActionsHTML(): string { global $spbc; + + $show_log_actions = ( + ! empty($spbc->data['scanner']['last_scan']) || + (function_exists('spbc_scanner__isScannerIsInProgress') && spbc_scanner__isScannerIsInProgress()) + ); + $_text = ' -
+
{{loading_logs_text}} -