diff --git a/css/cleantalk-admin-settings-page.min.css b/css/cleantalk-admin-settings-page.min.css index ce92c121a..f9483cb7a 100644 --- a/css/cleantalk-admin-settings-page.min.css +++ b/css/cleantalk-admin-settings-page.min.css @@ -1 +1 @@ -.apbct_settings-page{padding-right:10px}.apbct_settings-field_content,.cleantalk_link-auto,.cleantalk_link-manual,.ct-warning-test-failed,.ct_rate_block,.ct_settings_banner,i.animate-spin{display:inline-block}#apbctTopWarning{margin-bottom:5px}#apbctTopWarning h3{margin:10px 0 5px 0}#apbctTopWarning h4{margin:10px}#apbctTopWarning h4 span{margin-top:5px}.apbct_settings-subtitle{position:relative;top:-15px;margin:0}.apbct_settings-field_wrapper{margin:15px 0}.apbct_settings-field_wrapper--sub{margin-left:30px!important}.apbct_settings-field_wrapper--sub-sub{margin-left:60px!important}.apbct_settings__label{margin-right:10px;font-size:17px;vertical-align:text-bottom}.apbct_settings-field_content{display:inline-block}.apbct_settings-field_content--radio,.apbct_settings-field_wrapper>.apbct_settings-field_description{width:70%;padding-bottom:10px}.apbct_settings-field_title--select,.apbct_settings-field_title--textarea{margin:0;width:210px;padding-right:10px;padding-bottom:10px;font-size:14px;vertical-align:top}.apbct_settings-field_title--with-help{width:auto;white-space:nowrap}.apbct_settings-field_title--radio{display:inline-block;margin:0;width:210px;padding-right:10px;font-size:14px;vertical-align:top}.apbct_input_text{min-width:255px;width:400px}.apbct_settings-long_description---show:hover{color:#aaa;cursor:pointer}.apbct_setting_textarea{min-width:300px}.cleantalk_link{text-decoration:none;font-size:13px;line-height:26px;margin:10px 0 0 0;padding:0 10px 1px;cursor:pointer;border-width:1px;border-style:solid;-webkit-appearance:none;white-space:nowrap;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.cleantalk_link-auto{background:#ccc;border-color:#999;-webkit-box-shadow:inset 0 1px 0 rgba(200,200,200,.5),0 1px 0 rgba(0,0,0,.15);box-shadow:inset 0 1px 0 rgba(200,200,200,.5),0 1px 0 rgba(0,0,0,.15);color:#000;display:inline-block;height:28px;-webkit-border-radius:2px;border-radius:2px}.cleantalk_link-auto:hover{color:#fff}.cleantalk_link-auto.cleantalk_link_text_center{text-align:center}.cleantalk_link-manual{background:#2ea2cc;border-color:#0074a2;-webkit-box-shadow:inset 0 1px 0 rgba(120,200,230,.5),0 1px 0 rgba(0,0,0,.15);box-shadow:inset 0 1px 0 rgba(120,200,230,.5),0 1px 0 rgba(0,0,0,.15);color:#fff;display:inline-block;-webkit-border-radius:3px;border-radius:3px;text-align:center}.cleantalk_link-manual:hover{color:#000}.cleantalk_link[disabled=disabled]{background:#5d9db5;color:#000}.apbct_status_icon{vertical-align:text-bottom;margin:0 5px 0 8px}.apbct-icon-upload-cloud{margin-left:3px}a.ct_support_link{color:#666;margin-right:.5em;font-size:10pt;font-weight:400}.ct-warning-test-failed{display:inline-block;position:relative;padding:5px;margin:4px;border:3px solid #f03232;border-radius:5px;background-color:#ffc8c8}.ct_settings_banner{text-align:right;display:inline-block;width:100%;margin:1em 0;vertical-align:top}#ct_translate_plugin{margin-left:0}.ct_rate_block{display:inline-block;width:370px;margin-right:3em;padding:.8em .8em 15px .8em;text-align:center;border:1px dashed #666}#ct_translate_plugin .apbct_button_rate{margin-bottom:10px}.apbct_long_desc{position:absolute;background:#5a5a5a;min-width:80px;min-height:80px;max-width:500px;padding:10px;color:#fff;z-index:10}.apbct_long_desc a{color:#78c8e6}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:#78c8e6;margin:25px}@keyframes spin{to{transform:rotate(359deg)}}.apbct_long_desc__cancel{position:absolute;top:5px;right:5px;color:rgba(255,255,255,.5)}.apbct_long_desc__cancel:hover{color:#fff}.apbct_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%}.apbct_long_desc__title{color:#78c8e6;margin:0}.--hide{display:none}.apbct_preloader_button{height:15px;margin-left:5px;vertical-align:text-top;display:none}.--invisible{visibility:hidden}.apbct_preloader{height:1em;width:1em;margin-left:.5em;display:inline-block}.key_changed_sync{font-size:1.5em;line-height:2em;vertical-align:middle}.key_changed_success{font-size:1.5em;line-height:2em;vertical-align:middle}.--upper-case{text-transform:uppercase}#cleantalk-modal-content{text-align:center}#cleantalk-modal-content>*{display:block;width:100%}.cleantalk-confirm-modal_header{font-size:15px;font-weight:500}.cleantalk-confirm-modal_text-block{padding:10px 0}.cleantalk-confirm-modal_buttons-block{display:flex!important;gap:10px;justify-content:center}button.ct_support_link{border:none;background:0 0;color:#666;text-decoration:underline;cursor:pointer}.apbct-btn-as-link{padding:0;margin:0;vertical-align:baseline;color:gray;border:0 none;border-bottom:1px solid;cursor:pointer}#apbct-account-email[contenteditable=true]{padding:6px;background-color:#fff;border:1px solid #ccc}button[value=save_changes]:disabled{color:#2271b1;border-color:#2271b1;background:#f6f7f7}#apbct_settings__advanced_settings{position:relative}#apbct_settings__advanced_settings_inner{width:70%}#apbct_hidden_section_nav{position:absolute;top:0;right:0;width:200px}#apbct_hidden_section_nav ul{z-index:9999;width:100%;top:40px}.apbct_settings__position_main_save_button{text-align:right;margin-right:40px}#apbct_settings__after_advanced_settings{margin-bottom:20px;width:70%}#apbct_settings__button_section{position:static;z-index:9999;padding-bottom:40px;bottom:0}.apbct_highlighted{outline-offset:5px;outline-color:red;outline-width:3px!important;outline-style:solid!important}.apbct_notice_inner{display:flex;margin-top:10px!important}.apbct_notification__advanced_settings{margin:20px 0;font-weight:400}.apbct_settings_top_info__div{float:right;padding:0 20px 0 0;font-size:13px;position:relative}.apbct_settings_top_info__p{padding:0;margin:2px}.apbct_settings_top_info__btn{display:inline-flex;gap:10px}.apbct_hidden_section_nav_mob_btn{display:none;background-image:url(images/menu.svg);background-repeat:no-repeat;background-size:contain;background-position-y:center}.apbct_hidden_section_nav_mob_btn-close{display:none}.apbct_settings__footer{background:#c2c2c2;position:absolute;left:0;padding-bottom:30px;padding-left:21px;z-index:-1}.apbct_settings_top_info__sub_btn{display:flex;flex-wrap:wrap;gap:10px;margin:15px 0}.apbct_settings__key_line{display:flex;align-items:center;justify-content:flex-start;flex-direction:row}.apbct_settings__key_line__elements{margin-right:10px}.apbct_setting__key_line__buttons{margin:0}.apbct_setting__key_line__get_key_manual_chunk{margin:10px 0 10px 0;border:1px solid #d3d3d3;border-radius:3px;padding:10px;background:#fff}.apbct_settings__key_line__service_block{margin-left:10px}#apbct_settings__public_offer{margin-top:10px;display:flex;align-items:center}#cleantalk_apikey_wrapper{max-width:65%}.apcbt_contact_data_encoder__line{max-width:70%;display:flex;flex-wrap:wrap;flex-direction:row}.apbct_warning_red_point{display:inline-block;width:4px;height:4px;background:red;border-radius:100%;margin:0 10px 3px 3px;vertical-align:middle;position:relative}.apbct_warning_red_point::after{content:'';position:absolute;left:50%;top:50%;width:12px;height:12px;border:1.5px solid rgba(255,2,2,.65);border-radius:50%;animation:apbct_pulsate 1.2s ease-out infinite;opacity:.7;pointer-events:none;transform:translate(-50%,-50%)}#apbct_summary_and_support{display:flex;flex-wrap:wrap;width:-webkit-fill-available;border:1px solid #ddd;border-radius:3px;padding:10px;background:#ededed;margin-top:4em}#apbct_summary_and_support-left_side{width:69%;padding:0 20px 0 0}#apbct_summary_and_support-right_side{width:31%;padding:0 20px;border-left:1px solid #dcdcde}#apbct_summary_and_support-sides_wrap{display:flex;flex-direction:row;margin:10px 0 10px;width:100%}.apbct_summary_and_support-side_header{margin:.5em 0}.apbct_summary_and_support-inner_header{margin:2em 0 .5em;font-size:medium}.apbct_summary_and_support-inner_header::after{content:'';display:block;width:100%;height:1px;background:linear-gradient(90deg,#d3d3d357 0,rgba(211,211,211,.14) 10%,transparent 100%);margin-top:5px}.apbct_summary_and_support-support_buttons_wrapper{display:flex;flex-direction:column}#apbct_summary_and_support-create_user_button{display:inline-flex;flex-direction:row;align-items:center;justify-content:center}.apbct_summary_and_support-user_creation_result{background:#eee;margin-top:10px;padding:5px;border:1px solid #d3d3d3;display:none}.apbct_summary-list_of_items{display:flex;flex-direction:column}.apbct_summary_list_item{margin-bottom:2px}.apbct-green{color:green}.apbct-red{color:red}#apbct_negative_reports_table{width:100%;border-collapse:collapse;margin:10px 5px}#apbct_sending_report_div{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;flex-wrap:nowrap}#apbct_sending_report_disabled_notice{margin-right:5px}.apbct-real-user-popup-img{align-self:start;margin:0!important;height:1.1em;vertical-align:initial}@keyframes apbct_pulsate{0%{transform:translate(-50%,-50%) scale(1);opacity:.7}100%{transform:translate(-50%,-50%) scale(1.5);opacity:0}}@media (max-width:768px){.apbct_settings_top_info__btn{display:inline-grid;width:100%;gap:5px}#apbct_settings__advanced_settings_inner,#apbct_settings__after_advanced_settings{width:100%}.apbct_settings-field_content--radio,.apbct_settings-field_wrapper>.apbct_settings-field_description{width:100%}.apbct_settings_top_info__sub_btn .apbct_bottom_links--left,.apbct_settings_top_info__sub_btn .apbct_bottom_links--other{margin-left:0;margin-right:0}#apbct_hidden_section_nav ul{display:none;background-color:#dadee3bd;margin:0;padding:10px;border-radius:3px;right:0;width:auto}#apbct_hidden_section_nav{right:0;min-width:167px}.apbct_setting_radio::before{height:6px!important;width:6px!important;margin:4px!important}.apbct_setting_checkbox::before{height:15px!important;width:15px!important;margin:0 -.05rem!important}.apbct_setting_checkbox,.apbct_setting_radio{height:16px!important;width:16px!important}.apbct_hidden_section_nav_mob_btn{display:block;width:32px;height:28px;right:20px;top:20px;position:fixed}.apbct_hidden_section_nav_mob_btn-close{display:block;position:relative;float:right;width:20px;height:22px;top:-5px;right:-5px;background-image:url(images/cancel.svg);background-repeat:no-repeat;background-size:contain;background-position-y:center;text-align:center}.apbct_long_desc{left:-2%!important;max-width:95%;margin-top:10%}.apbct_long_desc__angle{display:none}.ct_rate_block{max-width:240px;margin-right:20px}.apbct_settings__key_line{display:flex;align-items:flex-start;flex-direction:column;width:100%}.apbct_settings__key_line__elements{margin:5px 0 5px 0}#apbct_setting_apikey,.apbct_setting__key_line__buttons,.apbct_settings__key_line__elements{width:100%}#cleantalk_apikey_wrapper{max-width:100%}#apbct_settings__button_section{position:fixed;right:20px}.apcbt_contact_data_encoder__line{max-width:100%;margin-top:5px}#apbct_summary_and_support-right_side{border-top:1px solid #dcdcde;border-left:0;margin:35px 0 0;padding:0;width:-webkit-fill-available}#apbct_summary_and_support-left_side{border-top:1px solid #dcdcde;width:-webkit-fill-available}#apbct_summary_and_support-sides_wrap{flex-direction:column}}.apbct-trial-renew-fullpage{display:flex;position:fixed;width:stretch;top:0;bottom:0;overflow-y:auto;justify-content:center;align-items:flex-start;font-family:Inter,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;color:#444;background-color:#f0f0f0;z-index:99}.apbct-trial-renew-fullpage-notice{padding:32px 64px 64px 64px!important;margin-top:5em!important;margin-bottom:5em!important}.apbct-trial-renew-fullpage-body{margin-top:32px;margin-left:32px}.apbct-banner-header{display:flex;justify-content:space-between}.apbct-trial-renew-fullpage-body .apbct-banner-button-wrapper{display:flex;margin:0}.apbct-trial-renew-fullpage-image{flex:1 1 0;min-width:0;max-width:576px;height:auto;object-fit:contain;margin:auto 0;align-self:flex-start}.apbct-banner-desc-block-row{line-height:22px}.apbct-banner-red-point{display:inline-block;width:8px;height:8px;background-color:#bb3323;border-radius:50%;margin-right:8px;vertical-align:middle}.apbct-banner-error{background-color:#fff!important;box-shadow:0 4px 15px #84848440!important;border-left-color:#bb3323!important}.apbct-banner-background-container{display:grid;max-width:644px;gap:16px;padding:32px;background-color:#f3f6f9;border-radius:8px}.apbct-banner-desc-block-title{font-size:18px;font-weight:700;line-height:22px;color:#0a0a0a;margin-bottom:8px}.apbct-banner-stat-value{margin-bottom:0;color:#444;vertical-align:middle}.apbct-banner-desc-block-text{font-size:14px;line-height:22px;color:#717182;margin-left:12px;margin-top:0;vertical-align:middle}.apbct-banner-desc-blocks{display:grid;gap:16px;flex:1 1 0;min-width:0}.apbct-banner-desc-blocks-content{display:flex;gap:72px;flex-wrap:wrap;justify-content:center;margin:32px 0}.apbct-banner-check{vertical-align:middle}.apbct-trial-renew-upgrade{display:none;text-align:center}.apbct-trial-renew-upgrade-link{font-size:16px;line-height:22px;letter-spacing:0;text-decoration:underline solid 0 #026e88!important;text-underline-offset:25%;color:#026e88!important}@media (max-width:1366px){.apbct-banner-header{display:grid;gap:16px}.apbct-banner-desc-blocks-content{display:grid;gap:38px;place-items:center}.apbct-trial-renew-upgrade{display:block}}@media (max-width:1200px){.apbct-banner-desc-blocks-content{gap:32px}}@media (max-width:782px){.apbct-trial-renew-fullpage{padding-top:50px;padding-bottom:50px}.apbct-trial-renew-fullpage-notice{padding:24px 24px 32px 24px!important}.apbct-trial-renew-fullpage-body{margin-top:20px;margin-left:0}.apbct-banner-desc-blocks-content{gap:24px}.apbct-banner-background-container{padding:20px}.apbct-trial-renew-fullpage-image{width:100%}}@media (max-width:480px){.apbct-trial-renew-fullpage-notice{padding:16px 12px 24px 12px!important}.apbct-banner-background-container{padding:16px}.apbct-banner-desc-blocks-content{gap:16px}.apbct-banner-desc-block-title{font-size:16px}} \ No newline at end of file +.apbct_settings-page{padding-right:10px}.apbct_settings-field_content,.cleantalk_link-auto,.cleantalk_link-manual,.ct-warning-test-failed,.ct_rate_block,.ct_settings_banner,i.animate-spin{display:inline-block}#apbctTopWarning{margin-bottom:5px}#apbctTopWarning h3{margin:10px 0 5px 0}#apbctTopWarning h4{margin:10px}#apbctTopWarning h4 span{margin-top:5px}.apbct_settings-subtitle{position:relative;top:-15px;margin:0}.apbct_settings-field_wrapper{margin:15px 0}.apbct_settings-field_wrapper--sub{margin-left:30px!important}.apbct_settings-field_wrapper--sub-sub{margin-left:60px!important}.apbct_settings__label{margin-right:10px;font-size:17px;vertical-align:text-bottom}.apbct_settings-field_content{display:inline-block}.apbct_settings-field_content--radio,.apbct_settings-field_wrapper>.apbct_settings-field_description{width:70%;padding-bottom:10px}.apbct_settings-field_title--select,.apbct_settings-field_title--textarea{margin:0;width:210px;padding-right:10px;padding-bottom:10px;font-size:14px;vertical-align:top}.apbct_settings-field_title--with-help{width:auto;white-space:nowrap}.apbct_settings-field_title--radio{display:inline-block;margin:0;width:210px;padding-right:10px;font-size:14px;vertical-align:top}.apbct_input_text{min-width:255px;width:400px}.apbct_settings-long_description---show:hover{color:#aaa;cursor:pointer}.apbct_setting_textarea{min-width:300px}.cleantalk_link{text-decoration:none;font-size:13px;line-height:26px;margin:10px 0 0 0;padding:0 10px 1px;cursor:pointer;border-width:1px;border-style:solid;-webkit-appearance:none;white-space:nowrap;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.cleantalk_link-auto{background:#ccc;border-color:#999;-webkit-box-shadow:inset 0 1px 0 rgba(200,200,200,.5),0 1px 0 rgba(0,0,0,.15);box-shadow:inset 0 1px 0 rgba(200,200,200,.5),0 1px 0 rgba(0,0,0,.15);color:#000;display:inline-block;height:28px;-webkit-border-radius:2px;border-radius:2px}.cleantalk_link-auto:hover{color:#fff}.cleantalk_link-auto.cleantalk_link_text_center{text-align:center}.cleantalk_link-manual{background:#2ea2cc;border-color:#0074a2;-webkit-box-shadow:inset 0 1px 0 rgba(120,200,230,.5),0 1px 0 rgba(0,0,0,.15);box-shadow:inset 0 1px 0 rgba(120,200,230,.5),0 1px 0 rgba(0,0,0,.15);color:#fff;display:inline-block;-webkit-border-radius:3px;border-radius:3px;text-align:center}.cleantalk_link-manual:hover{color:#000}.cleantalk_link[disabled=disabled]{background:#5d9db5;color:#000}.apbct_status_icon{vertical-align:text-bottom;margin:0 5px 0 8px}.apbct-icon-upload-cloud{margin-left:3px}a.ct_support_link{color:#666;margin-right:.5em;font-size:10pt;font-weight:400}.ct-warning-test-failed{display:inline-block;position:relative;padding:5px;margin:4px;border:3px solid #f03232;border-radius:5px;background-color:#ffc8c8}.ct_settings_banner{text-align:right;display:inline-block;width:100%;margin:1em 0;vertical-align:top}#ct_translate_plugin{margin-left:0}.ct_rate_block{display:inline-block;width:370px;margin-right:3em;padding:.8em .8em 15px .8em;text-align:center;border:1px dashed #666}#ct_translate_plugin .apbct_button_rate{margin-bottom:10px}.apbct_long_desc{position:absolute;background:#5a5a5a;min-width:80px;min-height:80px;max-width:500px;padding:10px;color:#fff;z-index:10}.apbct_long_desc a{color:#78c8e6}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:#78c8e6;margin:25px}@keyframes spin{to{transform:rotate(359deg)}}.apbct_long_desc__cancel{position:absolute;top:5px;right:5px;color:rgba(255,255,255,.5)}.apbct_long_desc__cancel:hover{color:#fff}.apbct_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%}.apbct_long_desc__title{color:#78c8e6;margin:0}.--hide{display:none}.apbct_preloader_button{height:15px;margin-left:5px;vertical-align:text-top;display:none}.--invisible{visibility:hidden}.apbct_preloader{height:1em;width:1em;margin-left:.5em;display:inline-block}.key_changed_sync{font-size:1.5em;line-height:2em;vertical-align:middle}.key_changed_success{font-size:1.5em;line-height:2em;vertical-align:middle}.--upper-case{text-transform:uppercase}#cleantalk-modal-content{text-align:center}#cleantalk-modal-content>*{display:block;width:100%}.cleantalk-confirm-modal_header{font-size:15px;font-weight:500}.cleantalk-confirm-modal_text-block{padding:10px 0}.cleantalk-confirm-modal_buttons-block{display:flex!important;gap:10px;justify-content:center}button.ct_support_link{border:none;background:0 0;color:#666;text-decoration:underline;cursor:pointer}.apbct-btn-as-link{padding:0;margin:0;vertical-align:baseline;color:gray;border:0 none;border-bottom:1px solid;cursor:pointer}#apbct-account-email[contenteditable=true]{padding:6px;background-color:#fff;border:1px solid #ccc}button[value=save_changes]:disabled{color:#2271b1;border-color:#2271b1;background:#f6f7f7}#apbct_settings__advanced_settings{position:relative}#apbct_settings__advanced_settings_inner{width:70%}#apbct_hidden_section_nav{position:absolute;top:0;right:0;width:200px}#apbct_hidden_section_nav ul{z-index:9999;width:100%;top:40px}.apbct_settings__position_main_save_button{text-align:right;margin-right:40px}#apbct_settings__after_advanced_settings{margin-bottom:20px;width:70%}#apbct_settings__button_section{position:static;z-index:9999;padding-bottom:40px;bottom:0}.apbct_highlighted{outline-offset:5px;outline-color:red;outline-width:3px!important;outline-style:solid!important}.apbct_notice_inner{display:flex;margin-top:10px!important}.apbct_notification__advanced_settings{margin:20px 0;font-weight:400}.apbct_settings_top_info__div{float:right;padding:0 20px 0 0;font-size:13px;position:relative}.apbct_settings_top_info__p{padding:0;margin:2px}.apbct_settings_top_info__btn{display:inline-flex;gap:10px}.apbct_hidden_section_nav_mob_btn{display:none;background-image:url(images/menu.svg);background-repeat:no-repeat;background-size:contain;background-position-y:center}.apbct_hidden_section_nav_mob_btn-close{display:none}.apbct_settings__footer{background:#c2c2c2;position:absolute;left:0;padding-bottom:30px;padding-left:21px;z-index:-1}.apbct_settings_top_info__sub_btn{display:flex;flex-wrap:wrap;gap:10px;margin:15px 0}.apbct_settings__key_line{display:flex;align-items:center;justify-content:flex-start;flex-direction:row}.apbct_settings__key_line__elements{margin-right:10px}.apbct_setting__key_line__buttons{margin:0}.apbct_setting__key_line__get_key_manual_chunk{margin:10px 0 10px 0;border:1px solid #d3d3d3;border-radius:3px;padding:10px;background:#fff}.apbct_settings__key_line__service_block{margin-left:10px}#apbct_settings__public_offer{margin-top:10px;display:flex;align-items:center}#cleantalk_apikey_wrapper{max-width:65%}.apcbt_contact_data_encoder__line{max-width:70%;display:flex;flex-wrap:wrap;flex-direction:row}.apbct_warning_red_point{display:inline-block;width:4px;height:4px;background:red;border-radius:100%;margin:0 10px 3px 3px;vertical-align:middle;position:relative}.apbct_warning_red_point::after{content:'';position:absolute;left:50%;top:50%;width:12px;height:12px;border:1.5px solid rgba(255,2,2,.65);border-radius:50%;animation:apbct_pulsate 1.2s ease-out infinite;opacity:.7;pointer-events:none;transform:translate(-50%,-50%)}#apbct_summary_and_support{display:flex;flex-wrap:wrap;width:-webkit-fill-available;border:1px solid #ddd;border-radius:3px;padding:10px;background:#ededed;margin-top:4em}#apbct_summary_and_support-left_side{width:69%;padding:0 20px 0 0}#apbct_summary_and_support-right_side{width:31%;padding:0 20px;border-left:1px solid #dcdcde}#apbct_summary_and_support-sides_wrap{display:flex;flex-direction:row;margin:10px 0 10px;width:100%}.apbct_summary_and_support-side_header{margin:.5em 0}.apbct_summary_and_support-inner_header{margin:2em 0 .5em;font-size:medium}.apbct_summary_and_support-inner_header::after{content:'';display:block;width:100%;height:1px;background:linear-gradient(90deg,#d3d3d357 0,rgba(211,211,211,.14) 10%,transparent 100%);margin-top:5px}.apbct_summary_and_support-support_buttons_wrapper{display:flex;flex-direction:column}#apbct_summary_and_support-create_user_button{display:inline-flex;flex-direction:row;align-items:center;justify-content:center}.apbct_summary_and_support-user_creation_result{background:#eee;margin-top:10px;padding:5px;border:1px solid #d3d3d3;display:none}.apbct_summary-list_of_items{display:flex;flex-direction:column}.apbct_summary_list_item{margin-bottom:2px}.apbct-green{color:green}.apbct-red{color:red}#apbct_negative_reports_table,#apbct_negative_reports_table_api{width:100%;border-collapse:collapse;margin:10px 5px}#apbct_sending_report_div{display:flex;flex-direction:row;justify-content:flex-end;align-items:center;flex-wrap:nowrap}#apbct_sending_report_disabled_notice{margin-right:5px}.apbct-real-user-popup-img{align-self:start;margin:0!important;height:1.1em;vertical-align:initial}@keyframes apbct_pulsate{0%{transform:translate(-50%,-50%) scale(1);opacity:.7}100%{transform:translate(-50%,-50%) scale(1.5);opacity:0}}@media (max-width:768px){.apbct_settings_top_info__btn{display:inline-grid;width:100%;gap:5px}#apbct_settings__advanced_settings_inner,#apbct_settings__after_advanced_settings{width:100%}.apbct_settings-field_content--radio,.apbct_settings-field_wrapper>.apbct_settings-field_description{width:100%}.apbct_settings_top_info__sub_btn .apbct_bottom_links--left,.apbct_settings_top_info__sub_btn .apbct_bottom_links--other{margin-left:0;margin-right:0}#apbct_hidden_section_nav ul{display:none;background-color:#dadee3bd;margin:0;padding:10px;border-radius:3px;right:0;width:auto}#apbct_hidden_section_nav{right:0;min-width:167px}.apbct_setting_radio::before{height:6px!important;width:6px!important;margin:4px!important}.apbct_setting_checkbox::before{height:15px!important;width:15px!important;margin:0 -.05rem!important}.apbct_setting_checkbox,.apbct_setting_radio{height:16px!important;width:16px!important}.apbct_hidden_section_nav_mob_btn{display:block;width:32px;height:28px;right:20px;top:20px;position:fixed}.apbct_hidden_section_nav_mob_btn-close{display:block;position:relative;float:right;width:20px;height:22px;top:-5px;right:-5px;background-image:url(images/cancel.svg);background-repeat:no-repeat;background-size:contain;background-position-y:center;text-align:center}.apbct_long_desc{left:-2%!important;max-width:95%;margin-top:10%}.apbct_long_desc__angle{display:none}.ct_rate_block{max-width:240px;margin-right:20px}.apbct_settings__key_line{display:flex;align-items:flex-start;flex-direction:column;width:100%}.apbct_settings__key_line__elements{margin:5px 0 5px 0}#apbct_setting_apikey,.apbct_setting__key_line__buttons,.apbct_settings__key_line__elements{width:100%}#cleantalk_apikey_wrapper{max-width:100%}#apbct_settings__button_section{position:fixed;right:20px}.apcbt_contact_data_encoder__line{max-width:100%;margin-top:5px}#apbct_summary_and_support-right_side{border-top:1px solid #dcdcde;border-left:0;margin:35px 0 0;padding:0;width:-webkit-fill-available}#apbct_summary_and_support-left_side{border-top:1px solid #dcdcde;width:-webkit-fill-available}#apbct_summary_and_support-sides_wrap{flex-direction:column}}.apbct-trial-renew-fullpage{display:flex;position:fixed;width:stretch;top:0;bottom:0;overflow-y:auto;justify-content:center;align-items:flex-start;font-family:Inter,system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;color:#444;background-color:#f0f0f0;z-index:99}.apbct-trial-renew-fullpage-notice{padding:32px 64px 64px 64px!important;margin-top:5em!important;margin-bottom:5em!important}.apbct-trial-renew-fullpage-body{margin-top:32px;margin-left:32px}.apbct-banner-header{display:flex;justify-content:space-between}.apbct-trial-renew-fullpage-body .apbct-banner-button-wrapper{display:flex;margin:0}.apbct-trial-renew-fullpage-image{flex:1 1 0;min-width:0;max-width:576px;height:auto;object-fit:contain;margin:auto 0;align-self:flex-start}.apbct-banner-desc-block-row{line-height:22px}.apbct-banner-red-point{display:inline-block;width:8px;height:8px;background-color:#bb3323;border-radius:50%;margin-right:8px;vertical-align:middle}.apbct-banner-error{background-color:#fff!important;box-shadow:0 4px 15px #84848440!important;border-left-color:#bb3323!important}.apbct-banner-background-container{display:grid;max-width:644px;gap:16px;padding:32px;background-color:#f3f6f9;border-radius:8px}.apbct-banner-desc-block-title{font-size:18px;font-weight:700;line-height:22px;color:#0a0a0a;margin-bottom:8px}.apbct-banner-stat-value{margin-bottom:0;color:#444;vertical-align:middle}.apbct-banner-desc-block-text{font-size:14px;line-height:22px;color:#717182;margin-left:12px;margin-top:0;vertical-align:middle}.apbct-banner-desc-blocks{display:grid;gap:16px;flex:1 1 0;min-width:0}.apbct-banner-desc-blocks-content{display:flex;gap:72px;flex-wrap:wrap;justify-content:center;margin:32px 0}.apbct-banner-check{vertical-align:middle}.apbct-trial-renew-upgrade{display:none;text-align:center}.apbct-trial-renew-upgrade-link{font-size:16px;line-height:22px;letter-spacing:0;text-decoration:underline solid 0 #026e88!important;text-underline-offset:25%;color:#026e88!important}@media (max-width:1366px){.apbct-banner-header{display:grid;gap:16px}.apbct-banner-desc-blocks-content{display:grid;gap:38px;place-items:center}.apbct-trial-renew-upgrade{display:block}}@media (max-width:1200px){.apbct-banner-desc-blocks-content{gap:32px}}@media (max-width:782px){.apbct-trial-renew-fullpage{padding-top:50px;padding-bottom:50px}.apbct-trial-renew-fullpage-notice{padding:24px 24px 32px 24px!important}.apbct-trial-renew-fullpage-body{margin-top:20px;margin-left:0}.apbct-banner-desc-blocks-content{gap:24px}.apbct-banner-background-container{padding:20px}.apbct-trial-renew-fullpage-image{width:100%}}@media (max-width:480px){.apbct-trial-renew-fullpage-notice{padding:16px 12px 24px 12px!important}.apbct-banner-background-container{padding:16px}.apbct-banner-desc-blocks-content{gap:16px}.apbct-banner-desc-block-title{font-size:16px}} \ No newline at end of file diff --git a/css/src/cleantalk-admin-settings-page.css b/css/src/cleantalk-admin-settings-page.css index 28a2b9ce9..711d31678 100644 --- a/css/src/cleantalk-admin-settings-page.css +++ b/css/src/cleantalk-admin-settings-page.css @@ -562,7 +562,8 @@ button[value="save_changes"]:disabled { color: red; } -#apbct_negative_reports_table { +#apbct_negative_reports_table, +#apbct_negative_reports_table_api { width: 100%; border-collapse: collapse; margin: 10px 5px; diff --git a/lib/Cleantalk/ApbctWP/API.php b/lib/Cleantalk/ApbctWP/API.php index d137a851b..ca77589f1 100644 --- a/lib/Cleantalk/ApbctWP/API.php +++ b/lib/Cleantalk/ApbctWP/API.php @@ -3,6 +3,7 @@ namespace Cleantalk\ApbctWP; use Cleantalk\ApbctWP\HTTP\Request; +use Cleantalk\Common\HTTP\Response; /** * Class API. @@ -111,6 +112,43 @@ public static function sendRequest($data, $timeout = 10) ); } - return $request->request(); + $result = $request->request(); + + $http_code = $request->response instanceof Response + ? $request->response->getResponseCode() + : null; + self::handleConnectionReport($data, $result, $url, $http_code); + + return $result; + } + + /** + * Add the API request result to connection reports. Never breaks the API call. + * + * @param array $data + * @param mixed $result + * @param string $url + * @param int|null $http_code + */ + private static function handleConnectionReport($data, $result, $url, $http_code) + { + global $apbct; + + if ( ! $apbct instanceof State || ! defined('APBCT_TBL_CONNECTION_REPORTS') ) { + return; + } + + try { + $apbct->getConnectionReports()->handleApiRequest( + isset($data['method_name']) ? $data['method_name'] : 'unknown', + $data, + $result, + $url, + $http_code + ); + } catch ( \Throwable $e ) { + // Reporting failures must not affect the API request result + return; + } } } diff --git a/lib/Cleantalk/ApbctWP/ConnectionReports.php b/lib/Cleantalk/ApbctWP/ConnectionReports.php index e53011daf..41ef99fc4 100644 --- a/lib/Cleantalk/ApbctWP/ConnectionReports.php +++ b/lib/Cleantalk/ApbctWP/ConnectionReports.php @@ -10,8 +10,11 @@ class ConnectionReports { + const SOURCE_MODERATE = 'moderate'; + const SOURCE_API = 'api'; + /** - * Statistics of state + * Statistics of state (moderate requests) * @var int[] */ public $reports_count = array( @@ -21,6 +24,64 @@ class ConnectionReports 'stat_since' => 0 ); + /** + * Statistics of state (API requests) + * @var int[] + */ + public $api_reports_count = array( + 'positive' => 0, + 'negative' => 0, + 'total' => 0, + 'stat_since' => 0 + ); + + /** + * API methods which are not tracked. 2s_blacklists_db is handled by SFWUpdateSentinel. + * @var string[] + */ + private $api_methods_excluded = array( + '2s_blacklists_db', + ); + + /** + * Error prefixes from API::checkResponse() which mean the server answered (business errors, not connection ones) + * @var string[] + */ + private $api_business_errors_prefixes = array( + 'SERVER_ERROR', + 'NO_DATA', + 'COULDNT_ADD_WL_IP', + ); + + /** + * Request data keys that must never be stored in reports + * @var string[] + */ + private $api_sensitive_keys = array( + 'auth_key', + 'api_key', + 'user_token', + 'key', + 'token', + 'password', + ); + + /** + * Max length of a single API request field kept in report + * @var int + */ + private $api_field_max_length = 500; + + /** + * @var bool|null Cache for the table existence check + */ + private $table_exists = null; + + /** + * @var bool|null Cache for source/method_name columns existence (they appear after DB update) + */ + private $has_source_columns = null; + /** * Instance of DB object * @var DB @@ -34,10 +95,13 @@ class ConnectionReports private $cr_table_name; /** - * Limit of reports to keep - * @var int + * Limit of reports to keep, per source + * @var int[] */ - private $reports_limit = 20; + private $reports_limits = array( + self::SOURCE_MODERATE => 20, + self::SOURCE_API => 20, + ); /** * @var array Current reports data from DB @@ -77,18 +141,64 @@ public function __construct(DB $db, $cr_table_name) */ private function initializeReportsCount($apbct) { - $this->reports_count['positive'] = isset($apbct->data['connection_reports_count']['positive']) - ? $apbct->data['connection_reports_count']['positive'] - : 0; - $this->reports_count['negative'] = isset($apbct->data['connection_reports_count']['negative']) - ? $apbct->data['connection_reports_count']['negative'] - : 0; - $this->reports_count['total'] = isset($apbct->data['connection_reports_count']['total']) - ? $apbct->data['connection_reports_count']['total'] - : 0; - $this->reports_count['stat_since'] = isset($apbct->data['connection_reports_count']['stat_since']) - ? $apbct->data['connection_reports_count']['stat_since'] - : date('d M'); + $this->reports_count = $this->readCountFromData($apbct, 'connection_reports_count'); + $this->api_reports_count = $this->readCountFromData($apbct, 'connection_reports_count_api'); + } + + /** + * @param mixed $apbct + * @param string $data_key + * @return array + */ + private function readCountFromData($apbct, $data_key) + { + $stored = isset($apbct->data[$data_key]) && is_array($apbct->data[$data_key]) + ? $apbct->data[$data_key] + : array(); + + return array( + 'positive' => isset($stored['positive']) ? $stored['positive'] : 0, + 'negative' => isset($stored['negative']) ? $stored['negative'] : 0, + 'total' => isset($stored['total']) ? $stored['total'] : 0, + 'stat_since' => isset($stored['stat_since']) ? $stored['stat_since'] : date('d M'), + ); + } + + /** + * Check once if the reports table exists + * @return bool + */ + private function isTableExists() + { + if ($this->table_exists === null) { + $this->table_exists = $this->db->isTableExists($this->cr_table_name); + } + return $this->table_exists; + } + + /** + * Check once if the table has source/method_name columns + * @return bool + */ + private function hasSourceColumns() + { + if ($this->has_source_columns === null) { + $columns = $this->db->fetchAll('SHOW COLUMNS FROM ' . $this->cr_table_name . " LIKE 'method_name';"); + $this->has_source_columns = ! empty($columns); + } + return $this->has_source_columns; + } + + /** + * Get source of the report row. Legacy rows without source are moderate ones. + * @param array $report + * @return string + */ + private static function getReportSource($report) + { + return isset($report['source']) && $report['source'] === self::SOURCE_API + ? self::SOURCE_API + : self::SOURCE_MODERATE; } /** @@ -136,17 +246,51 @@ private function markReportsDataDirty() /** * Update global stats in state + * @param string $source */ - private function updateStats() + private function updateStats($source = self::SOURCE_MODERATE) { global $apbct; - $positive = isset($this->reports_count['positive']) ? $this->reports_count['positive'] : 0; - $negative = isset($this->reports_count['negative']) ? $this->reports_count['negative'] : 0; - $this->reports_count['total'] = $positive + $negative; - $apbct->data['connection_reports_count'] = $this->reports_count; + if ($source === self::SOURCE_API) { + $this->api_reports_count = $this->recalculateTotal($this->api_reports_count); + $apbct->data['connection_reports_count_api'] = $this->api_reports_count; + } else { + $this->reports_count = $this->recalculateTotal($this->reports_count); + $apbct->data['connection_reports_count'] = $this->reports_count; + } $apbct->saveData(); } + /** + * @param array $count + * @return array + */ + private function recalculateTotal($count) + { + $positive = isset($count['positive']) ? $count['positive'] : 0; + $negative = isset($count['negative']) ? $count['negative'] : 0; + $count['total'] = $positive + $negative; + return $count; + } + + /** + * Increment positive or negative counter of the source + * @param string $source + * @param string $type positive|negative + */ + private function incrementCount($source, $type) + { + if ($source === self::SOURCE_API) { + $this->api_reports_count[$type] = isset($this->api_reports_count[$type]) + ? $this->api_reports_count[$type] + 1 + : 1; + } else { + $this->reports_count[$type] = isset($this->reports_count[$type]) + ? $this->reports_count[$type] + 1 + : 1; + } + } + /** * Array of report's IDs that has null on sent_on field * @return array @@ -192,9 +336,10 @@ private function setReportsAsSent(array $ids) } /** - * Rotates reports in DB, remove the oldest ones by limit and age (6 months). + * Rotates reports of the source in DB, remove the oldest ones by limit and age (6 months). + * @param string $source */ - private function rotateReports() + private function rotateReports($source = self::SOURCE_MODERATE) { $reports_data = $this->getReportsData(); $ids_to_delete = array(); @@ -207,10 +352,14 @@ private function rotateReports() } } - // Remove oldest reports if exceeding limit - if (count($reports_data) >= $this->reports_limit) { - $overlimit = count($reports_data) - $this->reports_limit + 1; - $reports_to_del = array_slice($reports_data, 0, $overlimit); + // Remove oldest reports of the source if exceeding the source limit + $source_reports = array_values(array_filter($reports_data, static function ($report) use ($source) { + return self::getReportSource($report) === $source; + })); + $limit = isset($this->reports_limits[$source]) ? $this->reports_limits[$source] : 20; + if (count($source_reports) >= $limit) { + $overlimit = count($source_reports) - $limit + 1; + $reports_to_del = array_slice($source_reports, 0, $overlimit); $overlimit_ids = array_column($reports_to_del, 'id'); $ids_to_delete = array_unique(array_merge($ids_to_delete, $overlimit_ids)); @@ -259,12 +408,16 @@ private function getReportsDataByIds(array $ids) * @param string $failed_work_urls Current work URLs of CT server that failed * @param array $request_content CleanTalk request content * @param bool $post_blocked_via_js_check Flag if JS check passed request or not + * @param string $source Report source, moderate|api + * @param string|null $method_name API method name */ private function addReportToDb( $lib_report = '', $failed_work_urls = '', $request_content = array(), - $post_blocked_via_js_check = false + $post_blocked_via_js_check = false, + $source = self::SOURCE_MODERATE, + $method_name = null ) { $cr_data = array( 'date' => time(), @@ -272,28 +425,38 @@ private function addReportToDb( 'lib_report' => $lib_report, 'failed_work_urls' => $failed_work_urls, 'request_content' => json_encode(esc_sql($request_content)), - 'js_block' => $post_blocked_via_js_check ? '1' : '0' + 'js_block' => $post_blocked_via_js_check ? '1' : '0', + 'source' => $source, + 'method_name' => TT::toString($method_name), ); - $this->db->prepare( - "INSERT INTO " . $this->cr_table_name . " + $query = "INSERT INTO " . $this->cr_table_name . " SET date = %s, page_url = %s, lib_report = %s, failed_work_urls = %s, request_content = %s, - js_block = %s", - array( - $cr_data['date'], - $cr_data['page_url'], - $cr_data['lib_report'], - $cr_data['failed_work_urls'], - $cr_data['request_content'], - $cr_data['js_block'], - ) + js_block = %s"; + $params = array( + $cr_data['date'], + $cr_data['page_url'], + $cr_data['lib_report'], + $cr_data['failed_work_urls'], + $cr_data['request_content'], + $cr_data['js_block'], ); + if ($this->hasSourceColumns()) { + $query .= ", + source = %s, + method_name = %s"; + $params[] = $cr_data['source']; + $params[] = $cr_data['method_name']; + } + + $this->db->prepare($query, $params); + $this->db->execute($this->db->getQuery()); $this->markReportsDataDirty(); } @@ -312,58 +475,118 @@ public function prepareNegativeReportsHtmlForSettingsPage() return ''; } - $rows = ''; - $stat_since = isset($this->reports_count['stat_since']) ? $this->reports_count['stat_since'] : ''; - $total = isset($this->reports_count['total']) ? $this->reports_count['total'] : ''; - $positive = isset($this->reports_count['positive']) ? $this->reports_count['positive'] : ''; - $negative = isset($this->reports_count['negative']) ? $this->reports_count['negative'] : ''; - - $reports_html = '
From ' - . Escape::escHtml($stat_since) - . ' to ' . Escape::escHtml(date('d M')) . ' has been made ' - . Escape::escHtml($total) - . ' calls, where ' . Escape::escHtml($positive) . ' were success and ' - . Escape::escHtml($negative) . ' were negative -
| Failed connection reports | -||||||
|---|---|---|---|---|---|---|
| # | -Date | -Page URL | -Report | -Server IP | -Blocked via JS | -Status | -
| " . Escape::escHtml($title) . ' | ' + . '
|---|
' . Escape::escHtml($label) . ': from ' + . Escape::escHtml($stat_since) + . ' to ' . Escape::escHtml(date('d M')) . ' has been made ' + . Escape::escHtml($total) + . ' calls, where ' . Escape::escHtml($positive) . ' were success and ' + . Escape::escHtml($negative) . ' were negative
'; + } + + /** + * Get report status label and row color + * @param array $report + * @return string[] [status, color] + */ + private static function getReportStatus($report) + { + return isset($report['sent_on']) && $report['sent_on'] + ? array('Sent', 'gray') + : array('New', 'black'); + } + + /** + * Prepare single Anti-Spam report row HTML * @param int $key * @param array $report * @return string */ - private function prepareReportRow($key, $report) + private function prepareModerateReportRow($key, $report) { - // Determine status and color - if (isset($report['sent_on']) && $report['sent_on']) { - $status = 'Sent'; - $color = 'gray'; - } else { - $status = 'New'; - $color = 'black'; - } + list($status, $color) = self::getReportStatus($report); $report_date = isset($report['date']) ? $report['date'] : time(); $report_page_url = isset($report['page_url']) ? $report['page_url'] : ''; @@ -382,6 +605,33 @@ private function prepareReportRow($key, $report) . ''; } + /** + * Prepare single API report row HTML + * @param int $key + * @param array $report + * @return string + */ + private function prepareApiReportRow($key, $report) + { + list($status, $color) = self::getReportStatus($report); + + $report_date = isset($report['date']) ? $report['date'] : time(); + $report_page_url = isset($report['page_url']) ? $report['page_url'] : ''; + $report_method_name = isset($report['method_name']) ? $report['method_name'] : ''; + $report_lib_report = isset($report['lib_report']) ? $report['lib_report'] : ''; + $report_api_url = isset($report['failed_work_urls']) ? $report['failed_work_urls'] : ''; + + return 'No new reports.
'; + } + + $html = '| '; + foreach (array_keys($columns) as $header) { + $html .= ' | ' . Escape::escHtml($header) . ' | '; + } + $html .= 'Count | First date | Last date |
| ' . (++$counter) . '. | '; + foreach ($columns as $render_cell) { + $html .= '' . $render_cell($group['report']) . ' | '; + } + $html .= '' . Escape::escHtml($group['count']) . ' | ' + . '' . Escape::escHtml($group['first_date']) . ' | ' + . '' . Escape::escHtml($group['last_date']) . ' | ' + . '
From ' . Escape::escHtml($stat_since) . ' to ' . Escape::escHtml(date('d M')) . ' has been made ' . Escape::escHtml($total) . - ' calls, where ' . Escape::escHtml($positive) . ' were success and ' . Escape::escHtml($negative) . ' were negative
-Negative report:
-| - | Date | -Page URL | -Library report | -Server IP | -Blocked via JS | -
| ' . (++$counter) . '. | ' - . '' . TT::toString($report['date']) . ' | ' - . '' . Escape::escUrl($report['page_url']) . ' | ' - . '' . Escape::escHtml($report['lib_report']) . ' | ' - . '' . Escape::escHtml($report['failed_work_urls']) . ' | ' - . '' . ($report['js_block'] === '1' ? 'Yes' : 'No') . ' | ' - . '