SlimStat Analytics <= 5.3.5 - Unauthenticated Stored Cross-Site Scripting via 'fh'
Description
The SlimStat Analytics plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'fh' (fingerprint) parameter in all versions up to, and including, 5.3.5 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v5.4.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1238 (SlimStat Analytics Stored XSS) ## 1. Vulnerability Summary The **SlimStat Analytics** plugin (<= 5.3.5) is vulnerable to **Unauthenticated Stored Cross-Site Scripting (XSS)**. The vulnerability occurs because the plugin collects a visitor's browser "fing…
Show full research plan
Exploitation Research Plan: CVE-2026-1238 (SlimStat Analytics Stored XSS)
1. Vulnerability Summary
The SlimStat Analytics plugin (<= 5.3.5) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability occurs because the plugin collects a visitor's browser "fingerprint" via the fh parameter during tracking and stores it in the database (typically in the wp_slimstat table) without sufficient sanitization. When an administrator views the "Access Log" or "Reports" in the WordPress dashboard, this unsanitized fingerprint is rendered, executing any injected arbitrary JavaScript.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php(for AJAX tracking) orwp-json/wp-slimstat/v1/track(for REST tracking). - Action:
slimstat_track(AJAX) or the REST API tracking route. - Vulnerable Parameter:
fh(fingerprint hash). - Authentication: Unauthenticated (PR:N).
- Preconditions: The plugin must be active. By default, SlimStat tracks all visitors.
3. Code Flow
- Entry Point: A visitor (attacker) sends a request to the tracking endpoint.
- Tracking Logic: The plugin processes the tracking request. In version 5.3.x, this is handled by the tracking class (likely
SlimStat_Trackeror similar). - Data Extraction: The plugin retrieves parameters like
fh,sw(screen width),sh(screen height), andpl(page location) from the request. - Database Sink: The value of
fhis passed into a database insert query (e.g.,$wpdb->insert) into the{prefix}slimstattable. - Rendering (Admin Context): When an admin navigates to SlimStat > Access Log, the plugin retrieves entries from the database.
- XSS Trigger: The
fingerprintcolumn (populated byfh) is echoed in the "Access Log" table or a tooltip without usingesc_html()oresc_attr().
4. Nonce Acquisition Strategy
SlimStat often requires a nonce for tracking requests to prevent spam, even for unauthenticated visitors.
- Identify Shortcode: SlimStat usually enqueues its tracking script on all frontend pages by default. No specific shortcode is needed, but the homepage is the best target.
- Localize Script: The plugin uses
wp_localize_scriptto pass the tracking configuration towp-slimstat.js. - JS Variable: The data is typically stored in a global object. Based on common SlimStat patterns, it is likely
SlimStatParams. - Extraction Procedure:
- Use
browser_navigateto the WordPress homepage. - Use
browser_evalto extract the nonce:// Strategy: Check for common SlimStat localization objects const nonce = window.SlimStatParams?.ajax_nonce || window.SlimStatParams?.nonce; return nonce;
- Use
5. Exploitation Strategy
Step 1: Data Collection
Navigate to the homepage and extract the tracking endpoint URL and the nonce.
Step 2: Payload Injection
Send a POST request to the tracking endpoint.
- URL:
http://<target>/wp-admin/admin-ajax.php - Method: POST
- Headers:
Content-Type: application/x-www-form-urlencoded - Body:
Note: If the REST API tracker is enabled, the request would be sent toaction=slimstat_track&fh=<img src=x onerror=alert(document.domain)>&nonce=<NONCE>&sw=1920&sh=1080wp-json/wp-slimstat/v1/trackwith JSON data.
Step 3: Triggering the XSS
Log in as an Administrator and navigate to the Access Log.
- URL:
http://<target>/wp-admin/admin.php?page=slimstat-access-log
6. Test Data Setup
- Install Plugin: Install and activate SlimStat Analytics version 5.3.5.
- Standard Configuration: Ensure the plugin is tracking (enabled by default).
- Public Page: No specific setup needed, as tracking enqueues on the homepage.
7. Expected Results
- The tracking request should return a
200 OKor a success JSON response (e.g.,{"success": true}). - The
wp_slimstatdatabase table will contain a new row where thefingerprintcolumn contains the<img ...>payload. - When the Admin visits the Access Log, an alert box showing the domain will appear.
8. Verification Steps
Database Check (Post-Exploit)
Run a WP-CLI command to verify the payload is stored:
wp db query "SELECT fingerprint FROM $(wp db prefix)slimstat WHERE fingerprint LIKE '%onerror%';"
Response Check
The response from the admin-ajax.php tracking request should not indicate an error.
# Example success response structure
{"success":true,"data":"Tracking successful"}
9. Alternative Approaches
- Base64 Encoding: Some versions of SlimStat encode the tracking data in a single parameter (e.g.,
data=...). If the rawfhparameter is not accepted, capture a legitimate tracking request from the browser, decode the base64dataparameter, replace thefhvalue with the payload, re-encode, and replay. - REST API Endpoint: If
admin-ajax.phpis blocked or nonces fail, attempt to use the REST API endpoint:- URL:
wp-json/wp-slimstat/v1/track - Method: POST
- Body:
{"fh": "<script>alert(1)</script>", "pl": "http://target/"}
- URL:
- Heartbeat Endpoint: SlimStat also uses a heartbeat mechanism. The XSS might be injectable via the heartbeat parameters if they are stored similarly.
Summary
The SlimStat Analytics plugin for WordPress is vulnerable to unauthenticated Stored Cross-Site Scripting via the 'fh' (fingerprint) parameter in versions up to and including 5.3.5. This vulnerability exists because the plugin stores visitor fingerprint data without sanitization and fails to escape the data when rendering administrative reports, allowing for arbitrary code execution in the browser of a dashboard user.
Security Fix
Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: admin-bar-modal.css @@ -1 +1 @@ -.slimstat-postbox-custom-legend{display:flex;justify-content:flex-start;margin-top:20px;gap:40px;font-size:14px}#chartjs-tooltip{transition:0.3s all ease-in-out;pointer-events:none !important;background:white;z-index:9999;font:13px "Open Sans", sans-serif;border:solid 1px #e0e0e0;border-radius:12px;padding:10px 18px;margin:0;min-width:180px;max-width:320px;opacity:0.98;filter:drop-shadow(0 2px 8px rgba(60,60,90,0.1))}#chartjs-tooltip:hover{opacity:0.3}#chartjs-tooltip .slimstat-postbox-chart--item[data-index].active .tooltip-item-content{color:#000 !important}@media (max-width: 600px){#chartjs-tooltip{min-width:120px;max-width:98vw;font-size:12px;padding:7px 8px}}span.slimstat-toggle-prev-datasets{position:absolute;right:110px;max-width:80%;display:inline-block;color:#56585a;font-weight:600;vertical-align:top;font-size:12px;cursor:pointer;font-style:oblique;opacity:0.7}span.slimstat-toggle-prev-datasets.active{opacity:1}.slimstat-chart-controls{position:absolute;right:20px;text-align:right}.slimstat-chart-controls select{top:-3px;position:relative;display:inline-block;width:84px;min-height:11px !important;line-height:22px !important;border:none;text-decoration:underline dashed 1px}.slimstat-postbox-chart--items{display:flex;column-gap:25px;row-gap:20px;margin-bottom:5px;flex-wrap:wrap;padding:0px 0px 0px 20px;align-items:baseline}@media (max-width: 550px){.slimstat-postbox-chart--items{column-gap:40px}}.slimstat-postbox-chart--item{display:flex;flex-direction:row;gap:8px;align-items:center;cursor:pointer}.slimstat-postbox-chart--item:not(:last-child){margin-bottom:5px}.slimstat-postbox-chart--item.slimstat-postbox-chart--item-hidden{opacity:0.5}.slimstat-postbox-chart--item>span{font-size:13px;font-style:italic;font-weight:400;line-height:15.23px;color:#56585a;text-transform:capitalize}.slimstat-postbox-chart--item>span.slimstat-postbox-chart--item-label{font-weight:500;font-size:12px;font-style:italic}.slimstat-postbox-chart--item .slimstat-postbox-chart--item--color{display:inline-flex;width:15px;border-radius:2px;height:5px;margin-right:4px}.slimstat-postbox-chart--item .slimstat-postbox-chart--item--prev{font-size:9px;color:#616060;font-weight:bold;letter-spacing:-0.5px;margin:0 3px;top:-1px;position:relative}.slimstat-postbox-chart--item>div{display:flex;gap:16px;align-items:center;cursor:pointer}body.index-php #dashboard-widgets-wrap .postbox .slimstat-tooltip-trigger.corner.slimstat-toggle-prev-datasets{display:block;float:none;cursor:help;width:120px !important;max-width:120px !important;position:relative;left:unset;right:unset;text-align:left;min-width:200px !important;margin:5px 20px;margin-bottom:10px}body.index-php .slimstat-chart-wrap{max-width:100% !important;width:100%;aspect-ratio:16/7;height:auto}body.index-php .inside:has(.slimstat-postbox-chart--canvas){height:auto !important;min-height:180px;max-height:none;margin-bottom:20px !important}body.index-php .slimstat-postbox-chart--canvas{width:100% !important;max-width:100%;min-width:100px;aspect-ratio:16/7;height:auto !important;min-height:120px;max-height:60vw;border-radius:7px;margin:0 auto 8px auto;box-shadow:0 1px 4px rgba(60,60,90,0.04);display:block}body.index-php .slimstat-postbox-custom-legend,body.index-php .slimstat-postbox-chart--items{flex-direction:row;flex-wrap:wrap;gap:12px;column-gap:12px;row-gap:8px;padding-left:0;overflow-x:auto;font-size:13px;width:100%;box-sizing:border-box;justify-content:start;margin:5px 20px}body.index-php .slimstat-postbox-chart--item{padding:3px 7px;font-size:12px;border-radius:5px;margin-bottom:2px}body.index-php .slimstat-chart-controls{right:10px;position:absolute;display:inline-block;width:80px;text-align:left;left:unset}body.index-php .slimstat-chart-controls select{width:90px;font-size:12px;padding:1px 4px;border-radius:5px}@media (max-width: 900px){body.index-php .slimstat-postbox-chart--canvas{aspect-ratio:16/10;min-height:90px;max-height:50vw}}@media (max-width: 600px){body.index-php .slimstat-postbox-custom-legend,body.index-php .slimstat-postbox-chart--items{flex-direction:column;gap:8px;column-gap:8px;row-gap:6px;font-size:12px;width:100%}body.index-php .slimstat-postbox-chart--canvas{min-width:80px;aspect-ratio:16/12;min-height:60px;max-height:40vw;border-radius:5px}body.index-php .slimstat-chart-wrap{aspect-ratio:16/12;min-height:60px}}@media (max-width: 400px){body.index-php .slimstat-postbox-chart--canvas{min-width:60px;min-height:40px;max-height:30vw}body.index-php .slimstat-chart-wrap{min-height:40px}}[dir="rtl"] .slimstat-postbox-custom-legend,.rtl .slimstat-postbox-custom-legend{flex-direction:row-reverse;justify-content:flex-end;gap:40px;text-align:right}[dir="rtl"] .slimstat-chart-controls,.rtl .slimstat-chart-controls{right:auto;left:20px;text-align:left}[dir="rtl"] #chartjs-tooltip,.rtl #chartjs-tooltip{direction:rtl;text-align:right;font-family:"Open Sans", sans-serif !important}[dir="rtl"] .slimstat-postbox-custom-legend,.rtl .slimstat-postbox-custom-legend,[dir="rtl"] .slimstat-postbox-chart--items,.rtl .slimstat-postbox-chart--items{direction:rtl;text-align:right;font-family:"Open Sans", sans-serif !important}[dir="rtl"] .slimstat-postbox-chart--item,.rtl .slimstat-postbox-chart--item{direction:rtl;text-align:right;font-family:"Open Sans", sans-serif !important}[dir="rtl"] .tooltip-item-title,.rtl .tooltip-item-title,[dir="rtl"] .tooltip-item-content,.rtl .tooltip-item-content{direction:rtl;text-align:right;font-family:"Open Sans", sans-serif !important}[id^="slim_"] .inside:has(.slimstat-postbox-chart--canvas){margin-bottom:20px !important;height:265px !important}[id^="slim_"] .inside:has(.slimstat-postbox-chart--canvas) .slimstat-postbox-chart--canvas{height:240px !important;width:100% !important;min-height:240px !important;max-height:240px !important}.chart-placeholder{height:280px;overflow:hidden}[dir="rtl"] #chartjs-tooltip table,.rtl #chartjs-tooltip table{direction:rtl;text-align:right}[dir="rtl"] #chartjs-tooltip tr.slimstat-postbox-chart--item td,.rtl #chartjs-tooltip tr.slimstat-postbox-chart--item td{padding-right:0;padding-left:8px}[dir="rtl"] .slimstat-postbox-chart--item,.rtl .slimstat-postbox-chart--item{text-align:right;gap:8px}[dir="rtl"] #chartjs-tooltip tr.slimstat-postbox-chart--item{flex-direction:unset !important}[dir="rtl"] .slimstat-toggle-prev-datasets{left:120px !important;right:unset;direction:ltr}[dir="rtl"] .slimstat-postbox-chart--items{padding:0px 20px 0px 20px}[dir="rtl"] .slimstat-postbox-chart--item>span,.rtl .slimstat-postbox-chart--item>span{text-align:right}[dir="rtl"] .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color,.rtl .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color{margin-left:0px !important;margin-right:10px !important}[dir="rtl"] #chartjs-tooltip .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color,.rtl #chartjs-tooltip .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color{margin-left:10px !important;margin-right:10px !important}[dir="rtl"] .slimstat-postbox-chart--item:not(:first-child){margin-bottom:0px}[dir="rtl"] .slimstat-postbox-chart--item:not(:last-child){margin-bottom:0px}[dir="rtl"] .slimstat-postbox-chart--item .slimstat-postbox-chart--item--prev,.rtl .slimstat-postbox-chart--item .slimstat-postbox-chart--item--prev{margin:0 3px 0 0}[dir="rtl"] .tooltip-item-title,.rtl .tooltip-item-title{text-align:right}[dir="rtl"] .tooltip-item-content,.rtl .tooltip-item-content{direction:rtl;text-align:right}:root{--box-bar-color: #eff6ff}body.toplevel_page_slimview1,body.slimstat_page_slimview2,body.slimstat_page_slimview3,body.slimstat_page_slimview4,body.slimstat_page_slimview5,body.slimstat_page_slimconfig,body.slimstat_page_slimpro,body.slimstat_page_slimlayout{background:#f5f6fa !important}#slimstat-load-saved-filters{margin:0 5px !important}body.index-php .slimstat-tooltip-bar-wrap{margin:0 5px !important}body.index-php .slimstat-tooltip-trigger:has(.slimstat-tooltip-bar-wrap){border:none !important}body.index-php span.slimstat-tooltip-bar{display:block;height:100%;position:absolute !important;top:0;left:0;background-color:var(--box-bar-color);border-radius:6px}body.index-php .postbox p.slimstat-tooltip-trigger{margin-bottom:4px;margin-top:4px}body.index-php .slimstat-browser-icon,body.index-php span.slimstat-flag-container{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}body.index-php .slimstat-author-link img{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}body.index-php .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap{z-index:0 !important;position:absolute;display:block;width:calc(100% - 30px);height:100%;top:0;left:0;margin:0px 15px;box-sizing:border-box}body.index-php .slimstat-tooltip-trigger *{z-index:2 !important;position:relative}body.index-php .slimstat-tooltip-trigger a{max-width:80%;display:inline-block;color:#202224;font-weight:600;vertical-align:middle;font-size:12px}body.index-php .inside:has(.map-container){height:auto !important}body.index-php .map-container{display:flex;flex-wrap:wrap;padding:0px 20px;height:720px !important}body.index-php #map_slim_p6_01{width:100%;height:395px;min-height:395px;flex:1}@media (max-width: 768px){body.index-php #map_slim_p6_01{height:310px;min-height:310px;max-height:310px}}body.index-php .top-countries-wrap{min-width:370px;max-width:370px;margin-left:0px;display:flex;justify-content:center}@media (max-width: 768px){body.index-php .top-countries-wrap{margin-left:0;min-width:100%;max-width:100%}}body.index-php .top-countries{flex:1}body.index-php .top-countries h4{font-weight:700;font-size:14px;line-height:100%;letter-spacing:-0.43px}body.index-php .country-bar{display:flex;align-items:center;margin-bottom:20px;position:relative;height:36px}body.index-php .country-bar .country-flag-container{width:24px;height:24px;border-radius:100px;overflow:hidden;margin:0px 10px 0 0px;display:inline-block;min-width:24px}body.index-php .country-bar strong{font-weight:700;font-size:14px;line-height:100%;letter-spacing:0px;vertical-align:middle;position:absolute;top:3px;left:43px;color:#202224}body.index-php .country-bar span{color:#202224;font-weight:800;font-size:12px;line-height:100%;letter-spacing:0px;vertical-align:middle;margin-left:5px;margin-top:23px}body.index-php .country-flag{height:auto;transform:translateX(-13%)}body.index-php .bar-container{background-color:#e5e6e9;height:8px;width:100%;margin-left:10px;border-radius:4px;margin-top:25px;overflow:hidden}body.index-php .bar-fill{height:100%;background-color:#e7294b}body.index-php .map-container{flex-direction:column}body.index-php .top-countries{margin-left:0;margin-top:20px}[id^="slim_"] .inside:has(.slimstat-postbox-chart--canvas){margin-bottom:20px !important;height:265px !important}[id^="slim_"] .inside:has(.slimstat-postbox-chart--canvas) .slimstat-postbox-chart--canvas{height:240px !important;width:100% !important;min-height:240px !important;max-height:240px !important}#slim_p8_01 .inside,#slim_p8_02 .inside,.report-pages-by-user .inside{margin-bottom:10px !important}#slim_p8_01 .pagination,#slim_p8_02 .pagination,.report-pages-by-user .pagination{position:relative !important}.wrap.slimstat{overflow-x:hidden !important}.wrap.slimstat .postbox:has(p.loading) a.refresh svg{animation:spin 1s linear infinite}.wrap.slimstat .map-container{display:flex;flex-wrap:wrap;padding:0px 20px}@media (min-width: 782px){.wrap.slimstat .map-container{overflow:hidden;height:370px}}.wrap.slimstat #map_slim_p6_01{width:100%;height:395px;min-height:395px;flex:1}@media (max-width: 768px){.wrap.slimstat #map_slim_p6_01{height:310px;min-height:310px;max-height:310px}}.wrap.slimstat .top-countries-wrap{min-width:430px;max-width:430px;margin-left:30px;display:flex;justify-content:center}@media (max-width: 768px){.wrap.slimstat .top-countries-wrap{margin-left:0;min-width:100%;max-width:100%}}.wrap.slimstat .top-countries{flex:1}.wrap.slimstat .top-countries h4{font-weight:700;font-size:14px;line-height:100%;letter-spacing:-0.43px}.wrap.slimstat .country-bar{display:flex;align-items:center;margin-bottom:20px;position:relative;height:36px}.wrap.slimstat .country-bar .country-flag-container{width:24px;height:24px;border-radius:100px;overflow:hidden;margin:0px 10px 0 0px;display:inline-block;min-width:24px}.wrap.slimstat .country-bar strong{font-weight:700;font-size:14px;line-height:100%;letter-spacing:0px;vertical-align:middle;position:absolute;top:3px;left:43px;color:#202224}.wrap.slimstat .country-bar span{color:#202224;font-weight:800;font-size:12px;line-height:100%;letter-spacing:0px;vertical-align:middle;margin-left:5px;margin-top:23px}.wrap.slimstat .country-flag{height:auto;transform:translateX(-13%)}.wrap.slimstat .bar-container{background-color:#e5e6e9;height:8px;width:100%;margin-left:10px;border-radius:4px;margin-top:25px;overflow:hidden}.wrap.slimstat .bar-fill{height:100%;background-color:#e7294b}@media (max-width: 768px){.wrap.slimstat .map-container{flex-direction:column}.wrap.slimstat .top-countries{margin-left:0;margin-top:20px}}body.index-php .jqvmap-label,body.toplevel_page_slimview1 .jqvmap-label,body.slimstat_page_slimview2 .jqvmap-label,body.slimstat_page_slimview3 .jqvmap-label,body.slimstat_page_slimview4 .jqvmap-label,body.slimstat_page_slimview5 .jqvmap-label,body.slimstat_page_slimconfig .jqvmap-label,body.slimstat_page_slimpro .jqvmap-label,body.slimstat_page_slimlayout .jqvmap-label{z-index:999999;position:absolute !important;background-color:#2b2b2b !important;color:#ffffff !important;padding:10px !important;transform:translateY(-15px) translateX(50%) !important;max-width:280px !important;min-width:50px !important;font-size:1em !important;line-height:1.5em !important;direction:ltr !important;border-radius:8px !important;backdrop-filter:blur(34px) !important;box-shadow:0px 2px 4px 0px #0000000a !important}body.index-php .jqvmap-label h3,body.toplevel_page_slimview1 .jqvmap-label h3,body.slimstat_page_slimview2 .jqvmap-label h3,body.slimstat_page_slimview3 .jqvmap-label h3,body.slimstat_page_slimview4 .jqvmap-label h3,body.slimstat_page_slimview5 .jqvmap-label h3,body.slimstat_page_slimconfig .jqvmap-label h3,body.slimstat_page_slimpro .jqvmap-label h3,body.slimstat_page_slimlayout .jqvmap-label h3{margin:0 !important;font-size:16px !important;line-height:100% !important;font-weight:bold !important;background-color:#2b2b2b !important;color:#ffffff !important;letter-spacing:1px !important}body.index-php .jqvmap-label p,body.toplevel_page_slimview1 .jqvmap-label p,body.slimstat_page_slimview2 .jqvmap-label p,body.slimstat_page_slimview3 .jqvmap-label p,body.slimstat_page_slimview4 .jqvmap-label p,body.slimstat_page_slimview5 .jqvmap-label p,body.slimstat_page_slimconfig .jqvmap-label p,body.slimstat_page_slimpro .jqvmap-label p,body.slimstat_page_slimlayout .jqvmap-label p{font-family:Inter !important;font-weight:600 !important;font-size:12px !important;line-height:100% !important;letter-spacing:1px !important;background-color:#2b2b2b !important;color:#ffffff !important}body.index-php .jqvmap-label canvas,body.toplevel_page_slimview1 .jqvmap-label canvas,body.slimstat_page_slimview2 .jqvmap-label canvas,body.slimstat_page_slimview3 .jqvmap-label canvas,body.slimstat_page_slimview4 .jqvmap-label canvas,body.slimstat_page_slimview5 .jqvmap-label canvas,body.slimstat_page_slimconfig .jqvmap-label canvas,body.slimstat_page_slimpro .jqvmap-label canvas,body.slimstat_page_slimlayout .jqvmap-label canvas{position:absolute;color:#2b2b2b !important;background:#2b2b2b !important;border:0 dashed transparent;background-color:#2b2b2b !important;width:12px !important;height:12px !important;transform:rotate(135deg) translateX(0px) translateY(1px);line-height:unset !important;border-top-right-radius:4px;display:inline-block;bottom:-5px;left:calc(50% - 6px)}.export-pro-badge{margin-left:3px;margin-bottom:-5px}.wrap.slimstat a,[id^="slim_"] a{outline:none;text-decoration:none}[id^="slim_"] a.slimstat-delete-entry:before{color:#ff3636}.slimstat-float-right{float:right}#slimstat-filters-post{display:none}#slimstat-filters-form{margin-top:10px;padding:5px;position:relative;border-radius:5px}#slimstat-filters input.text,#slimstat-filters select{vertical-align:initial;width:15%}#screen-meta-links{position:absolute;right:0;z-index:1}.rtl #screen-meta-links{left:0;right:auto}#slimstat-date-filters{padding:4px 10px 6px 20px;position:absolute;right:5px;top:5px;background-color:#e8294c;border-radius:50px}#slimstat-date-filters>a{color:#fff;white-space:nowrap}#slimstat-date-filters>a::after{content:"\f140";font:normal 20px/1 "dashicons";vertical-align:bottom}#slimstat-date-filters>a.open::after{content:"\f142"}#slimstat-date-filters .dropdown{background-color:#fff;box-shadow:5px 0px 30px rgba(0,0,0,0.1);color:#222;display:none;padding:12px;position:absolute;right:-4px;top:35px;width:410px;z-index:120;border-radius:8px}.rtl #slimstat-date-filters .dropdown{right:auto;left:-4px}#slimstat-date-filters .dropdown strong{clear:both;display:block;font-weight:normal;padding:10px 5px 5px 0;text-transform:uppercase}#slimstat-date-filters .dropdown select,#slimstat-date-filters .dropdown input{margin:0 5px 5px 0;height:27px;width:130px}#slimstat-date-filters .dropdown select.short,#slimstat-date-filters .dropdown input.short{width:65px}#slimstat-date-filters .dropdown .ui-datepicker-trigger{float:right;margin:2px 5px 0 0}.rtl #slimstat-date-filters .dropdown .ui-datepicker-trigger{margin:2px 0 0 5px}#slimstat-current-filters{margin-bottom:5px;background-color:white;color:#616060;font-size:14px;overflow:hidden;padding:10px 10px;position:relative;border-radius:5px;margin-top:0px;border:solid 1px #f0f0f0}@media (max-width: 400px){#slimstat-current-filters .button-secondary{margin:2px auto}}#slimstat-current-filters .slimstat-remove-filter{color:#fff}#slimstat-current-filters .slimstat-filter-list{float:left;margin:0;padding:4px 0 5px 5px}#slimstat-current-filters .slimstat-filter-list li{display:inline;margin:0 20px 0 0;vertical-align:middle}.slimstat-filter-action-button{float:right;margin-left:10px !important}.wrap.slimstat .meta-box-sortables{display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;margin-right:-1.3%}@media (max-width: 1560px){.wrap.slimstat .meta-box-sortables{margin-right:-0.5%}}.rtl .wrap.slimstat .meta-box-sortables{margin-right:0;margin-left:-1.3%}@media (max-width: 1560px){.rtl .wrap.slimstat .meta-box-sortables{margin-left:-0.5%}}.wrap.slimstat .postbox,.wrap.slimstat .sortable-placeholder{box-sizing:border-box;float:left;margin-bottom:15px;min-width:24.3%;overflow:hidden;width:24.3%}.wrap.slimstat .postbox.large{width:49.1%}.wrap.slimstat .postbox.extralarge{width:73.9%}.wrap.slimstat .postbox.full-width{width:98.7%}.sortable-placeholder{background-color:#ccc;border:1px dashed #bbb;margin-bottom:9px}.wrap.slimstat .postbox h3{border-bottom:1px solid #ddd;font-size:1.2em;margin:0;padding:10px}.wrap.slimstat .postbox.tall h3{cursor:auto}.slimstat-tooltip-content{display:none}.slimstat .no-refresh .refresh{display:none}[id^="slim_"] p.pagination{font-weight:600;color:black;font-size:14px}[id^="slim_"] p.pagination a{color:#151515;float:right;margin-left:5px}[id^="slim_"] .inside{height:240px;margin:0 !important;overflow:auto;padding:0 !important;margin-bottom:45px !important}.wrap.slimstat .postbox.tall .inside{height:465px}.map-wrap .inside{height:370px !important}@media (max-width: 767px){.map-wrap .inside{height:auto !important}}[id^="slim_"] p{border-bottom:1px solid #eee;line-height:1.5em;margin:0;min-height:20px;padding:10px 10px;position:relative;word-wrap:break-word}[id^="slim_"] p:last-child{border-bottom:0}[id^="slim_"] p .slimstat-tooltip-trigger.corner{-moz-transform:scaleX(-1);-o-transform:scaleX(-1);-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH";left:0}[id^="slim_"] p span{float:right}[id^="slim_"] p span.pageview-screenres{margin-left:10px}[id^="slim_"] .inline-icon{background-color:transparent;background-position:0 0;background-repeat:no-repeat;display:inline-block;height:18px;line-height:18px;margin-right:5px;vertical-align:middle;width:16px;filter:grayscale(40%);transition:0.3s all ease-out}[id^="slim_"] .inline-icon:hover{filter:grayscale(0)}[id^="slim_"] .spaced{margin-left:15px}#dashboard-widgets-wrap .whois{display:none}[id^="slim_"] .debug{background-color:#000;color:#fff;display:block;font-family:monospace;opacity:0.8;padding:20px;position:relative}.little-color-box{background-color:#eee;border:1px solid #aaa;display:block;float:left;height:15px;margin-right:10px;width:15px}[id^="slim_"] .header{background-color:#f8f8f8;color:#111}[id^="slim_"] .header.is-search-engine,.little-color-box.is-search-engine{background-color:#e2dbff;color:#151515}[id^="slim_"] .header.is-direct,.little-color-box.is-direct{background-color:#f8f8f8;color:#111}[id^="slim_"] .header.is-known-user,.little-color-box.is-known-user{background-color:#ddf0ff}[id^="slim_"] .header.is-known-visitor,.little-color-box.is-known-visitor{background-color:#ffe9c8}[id^="slim_"] .header.is-spam,.little-color-box.is-spam{background-color:#eeff89;color:#222}[id^="slim_"] p.loading{text-align:center;width:100%;padding:0;margin:0;top:50%;line-height:0;transform:translateY(-50%);overflow:hidden}[id^="slim_"] p.nodata{border:0;color:#999;text-align:center;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:90%}.chart-placeholder{height:280px;overflow:hidden}body.toplevel_page_slimview1 div[class*="-notice"]:not(.slimstat-notice),body.toplevel_page_slimview1 div[class*="admin-notice"]:not(.slimstat-notice),body.toplevel_page_slimview1 .notice:not(.slimstat-notice),body.toplevel_page_slimview1 .update-nag:not(.slimstat-notice),body.toplevel_page_slimview1 .error:not(.slimstat-notice),body.slimstat_page_slimview2 div[class*="-notice"]:not(.slimstat-notice),body.slimstat_page_slimview2 div[class*="admin-notice"]:not(.slimstat-notice),body.slimstat_page_slimview2 .notice:not(.slimstat-notice),body.slimstat_page_slimview2 .update-nag:not(.slimstat-notice),body.slimstat_page_slimview2 .error:not(.slimstat-notice),body.slimstat_page_slimview3 div[class*="-notice"]:not(.slimstat-notice),body.slimstat_page_slimview3 div[class*="admin-notice"]:not(.slimstat-notice),body.slimstat_page_slimview3 .notice:not(.slimstat-notice),body.slimstat_page_slimview3 .update-nag:not(.slimstat-notice),body.slimstat_page_slimview3 .error:not(.slimstat-notice),body.slimstat_page_slimview4 div[class*="-notice"]:not(.slimstat-notice),body.slimstat_page_slimview4 div[class*="admin-notice"]:not(.slimstat-notice),body.slimstat_page_slimview4 .notice:not(.slimstat-notice),body.slimstat_page_slimview4 .update-nag:not(.slimstat-notice),body.slimstat_page_slimview4 .error:not(.slimstat-notice),body.slimstat_page_slimview5 div[class*="-notice"]:not(.slimstat-notice),body.slimstat_page_slimview5 div[class*="admin-notice"]:not(.slimstat-notice),body.slimstat_page_slimview5 .notice:not(.slimstat-notice),body.slimstat_page_slimview5 .update-nag:not(.slimstat-notice),body.slimstat_page_slimview5 .error:not(.slimstat-notice),body.slimstat_page_slimconfig div[class*="-notice"]:not(.slimstat-notice),body.slimstat_page_slimconfig div[class*="admin-notice"]:not(.slimstat-notice),body.slimstat_page_slimconfig .notice:not(.slimstat-notice),body.slimstat_page_slimconfig .update-nag:not(.slimstat-notice),body.slimstat_page_slimconfig .error:not(.slimstat-notice),body.slimstat_page_slimpro div[class*="-notice"]:not(.slimstat-notice),body.slimstat_page_slimpro div[class*="admin-notice"]:not(.slimstat-notice),body.slimstat_page_slimpro .notice:not(.slimstat-notice),body.slimstat_page_slimpro .update-nag:not(.slimstat-notice),body.slimstat_page_slimpro .error:not(.slimstat-notice),body.slimstat_page_slimlayout div[class*="-notice"]:not(.slimstat-notice),body.slimstat_page_slimlayout div[class*="admin-notice"]:not(.slimstat-notice),body.slimstat_page_slimlayout .notice:not(.slimstat-notice),body.slimstat_page_slimlayout .update-nag:not(.slimstat-notice),body.slimstat_page_slimlayout .error:not(.slimstat-notice){display:none !important}.ui-widget-overlay{background-color:#222;filter:alpha(opacity=60);height:100%;opacity:0.6;position:fixed;top:0;width:100%;z-index:100 !important}.ui-dialog.slimstat .ui-dialog-titlebar{background:#4b8df8;border:0;border-radius:0;color:#fff;font-family:"Open Sans", sans-serif;font-size:1.3em;font-weight:normal;height:22px;line-height:1.3em;margin:0;padding:5px 10px}.tooltip-item-title{font-size:13px;font-weight:500}.tooltip-item-content{font-size:13px;font-weight:normal;font-style:italic}.ui-dialog.slimstat .ui-dialog-titlebar-close{background-color:transparent;border:0;color:#fff;float:right;line-height:1.3em;padding:0}.ui-dialog.slimstat .ui-dialog-titlebar-close:before{content:"\e803";font-family:slimstat}.ui-dialog .ui-dialog-content{clear:both}#slimstat-modal-dialog{background-color:#fff;display:none;max-height:650px !important;overflow:auto;padding:0;width:auto}#slimstat-modal-dialog p{margin:0;padding:10px}#dashboard-widgets [id*="slim_p"].postbox .inside{height:281px}#dashboard-widgets [id*="slim_p"][id*="_01"].postbox .inside{height:290px}#dashboard-widgets #slim_p7_02.postbox .inside{height:320px}.closed .slimScrollDiv{height:0 !important}.nav-tabs{margin:20px 1px 0}.nav-tab{font-size:14px;margin:0 5px 0 0}.nav-tab a{color:#151515;display:block;text-decoration:none}.form-table{border:1px solid #ccc;margin-top:0}.form-table th{font-weight:400;padding:15px 10px}.slimstat-options-section-header{background-color:#ffffff !important;font-size:1.4em;font-weight:bold !important;margin:0;padding:5px 10px}.form-table h3{margin-top:0}.form-table td span.block-element{padding:0 30px 0 0}.form-table .description{color:#999;display:block;font-style:normal;margin-top:5px}.form-table #slimstat-filter-name,.form-table #slimstat-filter-operator,.form-table #slimstat-filter-value{width:20%}[id*="form-slimstat-options"] input[type="number"].small-text{width:85px}[class*="bootstrap-switch-id-addon_network_settings"]{float:right}.CodeMirror{height:auto !important}.wp-list-table.slimstat-addons{margin-bottom:20px}.wp-list-table.slimstat-addons tbody th{border-left:5px solid #ccc}.wp-list-table.slimstat-addons th,.wp-list-table.slimstat-addons td{border-bottom:1px solid #ccc}.wp-list-table.slimstat-addons .active th{border-color:#10a062;border-style:solid;border-radius:0 0 0 5px}.wp-list-table.slimstat-addons .active td{border:0}.column-wp-slimstat{text-align:center !important;width:3em}.slimstat-icon:before{content:"\f239";font-family:dashicons}.rtl #slimstat-current-filters .slimstat-filter-list{padding:0 0 0 75px}.rtl #slimstat-current-filters .slimstat-filter-list li{margin-left:20px;margin-right:0}.rtl #slimstat-remove-all-filters{left:5px;right:inherit}.rtl #slimstat-date-filters{left:5px;right:inherit}.rtl #slimstat-date-filters span{left:0;right:inherit}.rtl .wrap.slimstat .postbox{float:right}.rtl .slimScrollBar{left:2px !important;right:inherit !important}.rtl [id^="slim_"] p.pagination a{float:left}.rtl [id^="slim_"] p span{float:left}.rtl .qtip-content{text-align:right}.rtl .form-table td span.block-element{padding:0 0 0 30px}.slimstat-layout .postbox-container{float:none;margin-top:20px;overflow:hidden;border:solid 1px #f0f0f0;border-radius:10px}.slimstat-layout .meta-box-sortables{overflow:hidden;padding:10px}.slimstat-layout .postbox-container span.title{background-color:#c0c0c0;color:#fff;font-size:16px;margin-bottom:0;padding:10px}.slimstat-layout .postbox,.slimstat-layout .sortable-placeholder{float:left;margin:0 10px 10px 0;min-width:285px}.slimstat-layout h3{border:0;font-weight:300;font-size:1.1em;margin:0;padding:5px 10px}@media screen and (max-width: 1560px){.wrap.slimstat .postbox{width:32.8%}.wrap.slimstat .postbox.large{width:66.1%}.wrap.slimstat .postbox.extralarge,.wrap.slimstat .postbox.full-width{width:99.4%}.wrap.slimstat .postbox h3{font-size:1em;line-height:1.2em}.wrap.slimstat .postbox p{font-size:1em;line-height:1.5em}.wrap.slimstat .postbox p span.details{float:none !important;display:block;padding:0px !important;margin-top:10px;align-items:center}.wrap.slimstat .postbox p span.details>*:nth-last-child(1){position:relative;float:right;right:-25px}.wrap.slimstat .postbox p span.details>*:nth-last-child(2){position:relative;float:right;right:25px}[id^="slim_"] .spaced{margin:0}}.index-php div#slim_p7_02.postbox .pageview-screenres{float:none}.index-php div#slim_p7_02.postbox .details{position:relative;float:none;display:block}.index-php div#slim_p7_02.postbox .details .slimstat-font-edit{float:right}.index-php div#slim_p7_02.postbox .details .spaced{margin-left:0px}@media screen and (max-width: 1080px){.wrap.slimstat .postbox{width:49.125%}.wrap.slimstat .postbox.large,.wrap.slimstat .postbox.extralarge,.wrap.slimstat .postbox.full-width{width:99%}}@media screen and (max-width: 800px){#slimstat-filters input.text,#slimstat-filters select{margin:0 0.5% 0 0;width:26%}.wp-core-ui .button-secondary{height:35px}#slimstat-date-filters{margin-top:5px;position:relative;right:inherit;top:inherit}.wrap.slimstat .postbox,.wrap.slimstat .postbox.large,.wrap.slimstat .postbox.extralarge,.wrap.slimstat .postbox.full-width{margin:0 0 10px;width:99.5%}.wrap.slimstat .postbox h3{font-size:1.4em;line-height:1em}.wrap.slimstat .postbox p{line-height:1.4em}.nav-tab{display:block;margin:0}.form-table th{font-size:1.4em}.form-table th label{font-size:1em}.form-table td{padding:10px}.form-table .button-primary,.form-table .button-secondary{height:30px;margin-bottom:5px;text-align:center;width:100%}.form-table #slimstat-filter-name,.form-table #slimstat-filter-operator,.form-table #slimstat-filter-value{width:100%}[id^="slim_"] .users .column-name{display:none}[id^="slim_"] p{padding:8px 10px;word-break:break-all;overflow-wrap:break-word;hyphens:auto}[id^="slim_"] p .slimstat-filter-link{max-width:100%;display:inline-block;word-break:break-all;overflow-wrap:break-word}[id^="slim_"] p .details{display:block;margin-top:5px;font-size:0.9em;line-height:1.3em}[id^="slim_"] p .spaced{margin-left:8px;margin-right:8px}[id^="slim_"] .inline-icon{margin-right:3px}[id^="slim_"] p span.pageview-screenres{margin-left:5px;font-size:0.8em}[id^="slim_"] p .slimstat-filter-link{position:relative}[id^="slim_"] p .slimstat-filter-link[title]{cursor:help}[id^="slim_"] p{min-width:0;flex-shrink:1}[id^="slim_"] p .slimstat-filter-link{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;vertical-align:top}[id^="slim_"] p .slimstat-filter-link:hover{white-space:normal;word-break:break-all;overflow:visible;position:relative;z-index:10;background-color:#fff;box-shadow:0 2px 8px rgba(0,0,0,0.15);padding:4px;border-radius:3px}[id^="slim_"] p.access-log-entry{display:flex;flex-direction:column;align-items:flex-start;gap:4px}[id^="slim_"] p.access-log-entry .details{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:0.85em;color:#666;margin-top:4px}[id^="slim_"] p.access-log-entry{flex-direction:column;align-items:stretch}[id^="slim_"] p.access-log-entry .details{flex-direction:column;align-items:flex-start;gap:4px}[id^="slim_"] p.access-log-entry .details>*{margin-bottom:2px}}@media screen and (max-width: 600px){[id^="slim_"] p{padding:6px 8px;font-size:0.9em}[id^="slim_"] p .details{font-size:0.8em;margin-top:3px}[id^="slim_"] .spaced{margin-left:5px;margin-right:5px}[id^="slim_"] .inline-icon{margin-right:2px;width:14px;height:16px}[id^="slim_"] p span.pageview-screenres{font-size:0.7em;margin-left:3px}[id^="slim_"] p.header{display:flex;flex-direction:column;align-items:flex-start}[id^="slim_"] p.header .inline-icon{margin-bottom:2px}[id^="slim_"] p.access-log-entry .details{font-size:0.8em}}@font-face{font-family:"slimstat";src:url("slimstat.eot?58272494");src:url("slimstat.eot?58272494#iefix") format("embedded-opentype"),url("slimstat.svg?58272494#fontello") format("svg");font-weight:normal;font-style:normal}@font-face{font-family:"slimstat";src:url("data:application/octet-stream;base64,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") format("woff"),url("data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+UEk6AAABUAAAAFZjbWFwDgfuNQAAAagAAALOY3Z0IAcz/qQAACJEAAAAIGZwZ22KkZBZAAAiZAAAC3BnYXNwAAAAEAAAIjwAAAAIZ2x5ZilyI9cAAAR4AAAYQmhlYWQOp0OgAAAcvAAAADZoaGVhB8oD9gAAHPQAAAAkaG10eFtq//kAAB0YAAAAbGxvY2FIdk6NAAAdhAAAADhtYXhwAUkL+QAAHbwAAAAgbmFtZUhCRHEAAB3cAAADCXBvc3R0zYSJAAAg6AAAAVJwcmVw5UErvAAALdQAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDYwGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgA6BkDUv9qAFoDgQDGAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAGGAAEAAAAAAIAAAwABAAAALAADAAoAAAGGAAQAVAAAAAQABAABAADoGf//AADoAP//AAAAAQAEAAAAAQACAAMABAAFAAYABwAIAAkACgALAAwADQAOAA8AEAARABIAEwAUABUAFgAXABgAGQAaAAABBgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAFIAAAAAAAAABoAAOgAAADoAAAAAAEAAOgBAADoAQAAAAIAAOgCAADoAgAAAAMAAOgDAADoAwAAAAQAAOgEAADoBAAAAAUAAOgFAADoBQAAAAYAAOgGAADoBgAAAAcAAOgHAADoBwAAAAgAAOgIAADoCAAAAAkAAOgJAADoCQAAAAoAAOgKAADoCgAAAAsAAOgLAADoCwAAAAwAAOgMAADoDAAAAA0AAOgNAADoDQAAAA4AAOgOAADoDgAAAA8AAOgPAADoDwAAABAAAOgQAADoEAAAABEAAOgRAADoEQAAABIAAOgSAADoEgAAABMAAOgTAADoEwAAABQAAOgUAADoFAAAABUAAOgVAADoFQAAABYAAOgWAADoFgAAABcAAOgXAADoFwAAABgAAOgYAADoGAAAABkAAOgZAADoGQAAABoAAAACAAD/agM4A1IABwALAG1LsBFQWEAmAAEAAAFjAAQDBQMEBW0HAQUFbgIBAAMDAFICAQAAA1cGAQMAA0sbQCUAAQABbwAEAwUDBAVtBwEFBW4CAQADAwBSAgEAAANXBgEDAANLWUAUCAgAAAgLCAsKCQAHAAcREREIBRcrETUhNTMVIRUBESERASH2ASH88wLiAnmNTEyN/PECpP1cAAAAA//9/7EDXwMLABQAIQAuAEBAPQ4BAQIJAQIAAQJHAAIDAQMCAW0ABgADAgYDYAABAAAEAQBgAAQFBQRUAAQEBVgABQQFTBUWFRYjJiMHBRsrARUUBisBIiY9ATQ2OwE1NDY7ATIWFzQuAQ4DHgI+ATcUDgEiLgI+ATIeAQH0CgiyCAoKCH0KByQICuhSiqaMUAJUiKqGVntyxujIbgZ6vPS6fgIi+gcKCgckCArECAoKzFOKVAJQjqKOUAJUilN1xHR0xOrEdHTEAAAAAv///2oDoQMNAAgAIQAyQC8fAQEADgEDAQJHAAIDAnAABAAAAQQAYAABAwMBVAABAQNYAAMBA0wXIxQTEgUFGSsBNC4BBh4BPgEBFAYiLwEGIyIuAj4EHgIXFAcXFgKDlMyWBI7UjAEiLDoUv2R7UJJoQAI8bI6kjHA4A0W/FQGCZ5IClsqYBoz+mh0qFb9FPmqQoo5uOgRCZpZNe2S/FQAAAAABAAD/7wLUAoYAJAAeQBsiGRAHBAACAUcDAQIAAm8BAQAAZhQcFBQEBRgrJRQPAQYiLwEHBiIvASY0PwEnJjQ/ATYyHwE3NjIfARYUDwEXFgLUD0wQLBCkpBAsEEwQEKSkEBBMECwQpKQQLBBMDw+kpA9wFhBMDw+lpQ8PTBAsEKSkECwQTBAQpKQQEEwPLg+kpA8ABAAA//kDoQNSAAgAEQAnAD8AkkALPAEICQkAAgEAAkdLsAxQWEAzAAkICW8KAQgECG8ABwQFBAcFbQAFAAEFYwYBBAIBAAEEAGAAAQMDAVQAAQEDWQADAQNNG0A0AAkICW8KAQgECG8ABwQFBAcFbQAFAAQFAGsGAQQCAQABBABgAAEDAwFUAAEBA1kAAwEDTVlAED89OjglFiISJTkUExILBR0rJTQuAQYeAT4BNzQuAQ4BFj4BNxUUBgchIiYnNTQ2MyEXFjI/ASEyFgMWDwEGIi8BJjc2OwE1NDY3MzIWBxUzMgLKFB4WAhIiEJEUIBICFhwYRiAW/MsXHgEgFgEDSyFWIUwBAxYgtgoS+goeCvoRCQoXjxYOjw4WAY8YZA8UAhgaGAIUDw8UAhgaGAIUjLMWHgEgFbMWIEwgIEwgASgXEfoKCvoRFxX6DxQBFg76AAQAAP+xA6EDLgAIABEAKQBAAJRACzUBCQgJAAIBAAJHS7AJUFhAMwALCAtvCgEICQhvAAkFCW8ABgUAAQZlBwEFAgEAAQUAYAMBAQQEAVQDAQEBBFkABAEETRtANAALCAtvCgEICQhvAAkFCW8ABgUABQYAbQcBBQIBAAEFAGADAQEEBAFUAwEBAQRZAAQBBE1ZQBI9PDg2MzAjIjIlNRMUExIMBR0rJTQmDgEeATI2NzQmDgIWMjY3FRQGIyEiJic1NDYXMx4BOwEyNjczMhYDBisBFRQGByMiJic1IyImPwE2Mh8BFgLKFB4WAhIiEJEUIBICFhwYRiAW/MsXHgEgFu4MNiOPIjYN7hYgtgkYjxQPjw8UAY8XExH6Ch4K+hIdDhYCEiAUFBAOFgISIBQUjbMWICAWsxYgAR8oKB8eAVIW+g8UARYO+iwR+goK+hEAAAAC////+QQwAwsAGAAzAEJAPyoBAQYxIwUDAAECRwAGBQEFBgFtAgEAAQMBAANtAAUAAQAFAWAAAwQEA1QAAwMEWAAEAwRMIyg2FhQjIgcFGysBNCYrATU0JisBIgYdASMiBhQfARYyPwE2BRQGByEiJjc0NjcnNDYzMhYXNjMyFhUUBx4BAsoKCH0KB2wHCn0ICgXEBRAFxAUBZXxa/aFnlAFOQgGodleQISg1O1QXSF4BTAgKxAgKCgjEChAFxAUFxAZ2WXwBkmhIfB4YdqhiUCNUOysiEXYAAAAAAv////kEMAMLABgAMwBFQEIqAQAGMSMCAQANAQIBA0cABgUABQYAbQMBAQACAAECbQAFAAABBQBgAAIEBAJUAAICBFgABAIETCMoNRQjJRQHBRsrATQvASYiDwEGFBY7ARUUFjsBMjY9ATMyNgUUBgchIiY3NDY3JzQ2MzIWFzYzMhYVFAceAQLKBcQFEAXEBQoIfQoHbAcKfQgKAWV8Wv2hZ5QBTkIBqHZXkCEoNTtUF0heAXAIBcQFBcQGDwrECAoKCMQKmVl8AZJoSHweGHaoYlAjVDsrIhF2AAIAAP+xAjwDCwAIABgAJkAjAAEAAgABAm0AAgJuAAMAAANUAAMDAFgAAAMATBcXExIEBRgrATQmIgYUFjI2NxQHAw4BIiYnAyY1NDYyFgGtVHZUVHZUjhLLCSQmJgfMEqjsqAHtO1RUdlRUOz0n/lASFhYSAbAnPXaoqAACAAD/aQPoA1IADgAdAFFAThgUAgUGDgMCAQAAAQMBA0cVAQRFCAcCBQYABgUAbQIBAAEGAAFrAAQABgUEBmAAAQMDAVQAAQEDWAADAQNMDw8PHQ8dIhMkIhIiEQkFGysVESEHFjMyNjczBgQnIicDNiQzMhc3ESE3JiMiBgcBkqBsln3CIYoj/uyzz5KJIwEUs8+Tkv5uoGyWfcIhlgGSoGuWda3mAZIBxK7kkpL+bqBrlnUAAAAAAgAA//kDWQLEABgAQABQQE0MAQECAUchAQABRgADBwYHAwZtAAIGAQYCAW0AAQUGAQVrAAAFBAUABG0ABwAGAgcGYAAFAAQFVAAFBQRYAAQFBEwsJSonExYjFAgFHCsBFAcBBiImPQEjIiYnNTQ2NzM1NDYWFwEWNxEUBisBIiY3JyY/AT4BFzMyNjcRNCYnIyI0JjYvASY/AT4BFzMyFgKVC/7RCx4U+g8UARYO+hQeCwEvC8ReQ7IHDAEBAQECAQgIsiU0ATYktAYKAgIBAQECAQgIskNeAV4OC/7QChQPoRYO1g8UAaEOFgIJ/tAKtf54Q14KCAsJBg0HCAE2JAGIJTQBBAIIBAsJBg0HCAFeAAAAAgAA//kDawLDACcAQABCQD8UAQIBAUcABgIFAgYFbQAFAwIFA2sABAMAAwQAbQABAAIGAQJgAAMEAANUAAMDAFgAAAMATBYjGSUqJScHBRsrJRQWDwEOAQcjIiY1ETQ2OwEyFhUXFg8BDgEnIyIGBxEUFhczMh4CARQHAQYiJj0BIyImPQE0NjczNTQ2FhcBFgFlAgECAQgIskNeXkOyCAoBAQECAQgIsiU0ATYktAYCBgICBgv+0QscFvoOFhYO+hYcCwEvCy4CEgUOCQQBXkMBiENeCggLCQYNBwgBNiT+eCU0AQQCCAEsDgv+0AoUD6EWDtYPFAGhDhYCCf7QCgAAAAABAAAAAAFeAlEAFQAXQBQDAQABAUcAAQABbwAAAGYXGQIFFisBFA8BFxYUDwEGIicBJjQ3ATYyHwEWAV4G29sGBhwFDgb+/AYGAQQFEAQcBgIiBwXc2wYOBhwFBQEEBg4GAQQGBhwFAAEAAAAAAUwCUQAVABdAFAsBAAEBRwABAAFvAAAAZhwUAgUWKwEUBwEGIi8BJjQ/AScmND8BNjIXARYBTAb+/AUOBhwGBtvbBgYcBRAEAQQGAToHBv78BQUcBg4G29wFDgYcBgb+/AUAAgAAAAACNAJRABUAKwAcQBkpEwIAAQFHAwEBAAFvAgEAAGYXHRcUBAUYKyUUDwEGIicBJjQ3ATYyHwEWFA8BFxYXFA8BBiInASY0NwE2Mh8BFhQPARcWAV4GHAUOBv78BgYBBAUQBBwGBtvbBtYFHAYOBv78BgYBBAYOBhwFBdzcBVIHBhwFBQEEBg4GAQQGBhwFEATc2wYHBwYcBQUBBAYOBgEEBgYcBRAE3NsGAAACAAAAAAIiAlEAFQArABxAGSELAgABAUcDAQEAAW8CAQAAZhwYHBQEBRgrARQHAQYiLwEmND8BJyY0PwE2MhcBFhcUBwEGIi8BJjQ/AScmND8BNjIXARYBTAb+/AUOBhwGBtvbBgYcBRAEAQQG1gX+/AYOBhwFBdvbBQUcBg4GAQQFAToHBv78BQUcBg4G29wFDgYcBgb+/AUIBwb+/AUFHAYOBtvcBQ4GHAYG/vwFAAIAAP+xA1sDCwAkAEcAXUBaQyUCBgkvAQUGFwEDAggBAQMERwAJCAYICQZtBwEFBgIGBQJtBAECAwYCA2sAAQMAAwEAbQAIAAYFCAZgAAMBAANUAAMDAFgAAAMATEZFJiUlNiUmNRQkCgUdKwEUFQ4BIyImJwcGIiY9ATQ2OwEyFgYPAR4BMzI2NzY3NjsBMhYTFRQGKwEiJjY/ASYjIgYHBgcGKwEiJjc1PgEzMhYXNzYyFgNLJOSZUZg8SAscFhYO+g4WAglNKGQ3SoInBhcFDGsICg4UEPoOFgIJTVJwS4InBhcFDG8HDAEk5plRmjxICxwYAQUDAZa6PjlICxYO+g4WFhwLTSUoSj4KOA0MAbj6DhYWHAtNTUo+CjgNDAYElro+OUgLFgAAAAMAAP+xAsoDCwAIAA8AIwAyQC8PAQMCAUcABQACAwUCXgADAAEAAwFgAAAEBABSAAAABFgABAAETDU5ERMhEAYFGisXIREjIiYnNSEFMyYvASYnBREUBiMhIiYnETQ2MyEyFh8BHgFHAjzoFx4B/uIBZtEFB68GEAEdHhf9oRceASAWAWUWNg+uEBYHAawgFujWEAeuBwbk/gwWICAWAu4WIBgOrw82AAL///9bA+oDUgAfAEEAKUAmBAECAAFHMQEBRAMBAAIAbwACAQJvAAEBZgEAISAUEwAfAR8EBRQrASIHBgcxNjc2FxYXFhcWBgcGFx4BNz4BNzYmJy4BJyYBIgcGBwYHBhYXFhcWFxY3NjcxBgcGJyYnJicmNjc2JicmAfJXUVREVmxqZ2pPQiEhBiUOGhAzEQMKAiMBJSaQXlv+BRgPBAQGASQCJCZIW3t3eX1hVmxqZ2tPQiEgBSUIBg4SA1IdHjlFFRQeIE9CVlOzUSkbEAERAw8GWsNZXZAmJf7uEAQGCAZaw1ldSFskIhgZUUUVFB4gT0JWU7NRFSEOEgAAAAAFAAD/+QPkAwsAKQAuADUAPgBIAQBAEUg1NDMtLCsiCAUBHAEGBQJHS7AKUFhAMAAHAAEABwFtAAUBBgYFZQAAAAEFAAFgAAYIAQQCBgRfAAIDAwJUAAICA1gAAwIDTBtLsAtQWEApAAUBBgYFZQcBAAABBQABYAAGCAEEAgYEXwACAwMCVAACAgNYAAMCA0wbS7AXUFhAMAAHAAEABwFtAAUBBgYFZQAAAAEFAAFgAAYIAQQCBgRfAAIDAwJUAAICA1gAAwIDTBtAMQAHAAEABwFtAAUBBgEFBm0AAAABBQABYAAGCAEEAgYEXwACAwMCVAACAgNYAAMCA0xZWVlAEyoqQkEyMTAvKi4qLjw1ODMJBRgrNRE0NjchMhceAQ8BBicmIyEiBgcRFBYXITI2PQE0PwE2FgcVFAYjISImJTUBFwEnMxUzNycHNxY/ATYmDwEGEzc2Mh8BFhQPAV5DAdAjHgkDBxsICg0M/jAlNAE2JAHQJTQFJAgYAV5D/jBDXgFlAXeh/olrNSBAVUB0CQnECRIJxAn6MxAsEFUQEDOaAdBCXgEOBBMGHAgEAzQl/jAlNAE2JEYHBSQICAxqQ15eMaABd6D+iWs2QVVBZwkJxAkSCcQJAUEzEBBUECwQNAAABwAA/7ED6ALDABEAGgAjADUAPgBHAFAAYUBeNgEFBz8bAgQGLAECAwNHCQEFBwYHBQZtAAYEBwYEawgBBAMHBANrCwEDAgcDAmsAAAAHBQAHYAoBAgEBAlQKAQICAVgAAQIBTE9OS0pGRUJBPTw5OBMUExU3FAwFGis1ND4CMh4CFRQHBiMhIicmNxQWMj4BJg4BNxQWMjYuAQ4BEwYeATY3NiYnNzYuAQYPAQ4BExQWMjYuAQ4BFxQWMjYuAQ4BExQWMjYuAQ4BUIS8yLyEUE8KFPzyFApPRyo8KAIsOC5uKjosBCRCItULLFhKDQkaGzkDEBocAzghNhkqOiwEJEIi9io6LAQkQiJnKjosAig+Js9muIhOToi4ZpF8ERF7kh0qKjosAijbHSoqOiwCKP6XK0wYLishQBPVDhoGDBDVAywBlB0qKjosAiiKHSoqOiwCKP7nHSoqOiwCKAAAAAUAAP86A6oDgQAoADEAQgBLAFQAgEB9GwoCBAEfAQoGAAENCgNHAAQBBgEEBm0ABgoBBgprAAkNBw0JB20PAQoADQkKDWAABwAIDAcIYBABDAALBQwLYAMBAQECWAACAgxIDgEFBQBYAAAADQBJTUxEQyopUVBMVE1USEdDS0RLQD86NzQyLi0pMSoxGCMzKBQRBRkrARYVFAAEADU0Ejc1JzUjIiY+ATczMh4BBicjFQcVFhc/ATYyFgYPAQYBMjYQJgQGEBYTMzIWFAYnIyImPQE0NjIWBycyFhIGIiYSNhMyNi4BDgIWA1dT/uz+fv7s8LICMxUgAhwX0BUeAiITNAGccgYbDyogAg4aBf50l9bW/tLW1stoFSAgFZwVICAqIAE0gbYCuv68BLSDa5oCltqWApoCGXWUwv7uAgEWwLQBChMBAzMgKh4BICgiATMBAxFsCRoPHiwPGgX9hdYBLtYC0v7O0gGeHiogAR4WnBYeHhaduP7+uLgBArj9wprWmgKW2pYABQAA/2oD6ANSAB8AIgAlADMAPABwQG0jAQAGHQEJACcgAgcFA0cAAwAGAAMGXgwBAAAJBQAJXgAFAAcEBQdgAAQACggECmAACAACCwgCYA0BCwEBC1INAQsLAVgAAQsBTDQ0AQA0PDQ8Ozk2NTAvLiwpKCUkIiEaFw4MCQYAHwEeDgUUKwEyFhcRFAYHISImJzUhIiYnETQ2PwE+ATsBMhYXFTYzDwEzAQczFzc1IxUUBicjESE1NDYBESMVFAYnIxEDshceASAW/ekXHgH+0RceARYQ5A82FugXHgEmIUenp/6bp6dtsNYeF+kBHhYCJtceF+gCfCAW/VoXHgEgFqAgFgF3FjYP5BAWIBa3F3enAX2nwrDp6RYgAf6bjxY2/k4Cg+gWIAH+mgAAAwAA/7EEeAMMAAgALABPAHdAdCwlAgoHIB8OAwMCMhMCBAgDRwABBwFvAAcKB28OAQAKDQoADW0ACw0CDQsCbQwBCgANCwoNYAYBAgUBAwgCA2AACAQECFQACAgEWAkBBAgETAEATUtKSEVEQT82MzEvKSgkIhwbFxUSEAoJBQQACAEIDwUUKwEiJj4BHgIGBTMyFgcVFAYrARUUBgcjIiY9ASMiJic1NDY3MzU0NhczMhYXARQWNzMVBiMhIiY1ND4FFzIXHgEyNjc2MzIXIyIGFQGJWX4CerZ4BoQBw8QHDAEKCMQMBmsICsUHCgEMBsUKCGsHCgH+ZSodjyY5/hhDUgQMEh4mOiELCyxUZFQsCwtJMH0dKgFefrCAAny0ekkMBmsICsUHCgEMBsUKCGsHCgHEBwwBCgj+vx0sAYUcTkMeOEI2OCIaAgoiIiIiCjYqHQAAAAADAAD/sQRyAwwACAAsAE4AVEBRSQEAByQbEgMCCDIBBgIDRwABBAFvBQEEBwRvCQEHAAdvCgEACABvAAgCCG8DAQIGAm8ABgZmAQBIRkRDQT82MycmIiEVFBAPBQQACAEICwUUKwEiJj4BHgIGBRcWFA8BBiIvAQcGIi8BJjQ/AScmND8BNjIfATc2Mh8BFhQHBQcGFB8BBiMhIiY1ND4FFzIXFjI3NjMyFw4BBxQXAYlZfgJ6tngGhAIEiwUFTAUPBYuLBQ8FTAUFi4sFBUwFDwWLiwUPBUwFBf5fZRUVLgsN/hhDUgQMEh4mOiELC1a4VgsLDxAPDgEVAV5+sIACfLR6tYoGDwVMBQWLiwUFTAUPBoqLBQ8GSwUFi4sFBUsGDwWLZRQ8FS4CTkMeOEI2OCIaAgpERAoEDxoSHhUAAAIAAP9pA+oDUwAIAAwAHUAaAAADAG8AAwIDbwACAQJvAAEBZhESExIEBRgrETQABAACAAQANyE1IQEmAZwBKAT+4P5c/uLRAj79wgFezwEmAv7e/l7+3gIBJn2kAAAAAAEAAAABAACwdKvHXw889QALA+gAAAAA1YmAAAAAAADViYAA//3/OgR4A4EAAAAIAAIAAAAAAAAAAQAAA1L/agAABHb//f/6BHgAAQAAAAAAAAAAAAAAAAAAABsD6AAAAzgAAANZ//0DoP//AxEAAAOgAAADoAAABC///wQv//8COwAAA+gAAANZAAADoAAAAWUAAAFlAAACOwAAAjsAAANZAAACygAAA+n//wPoAAAD6AAAA6oAAAPoAAAEdgAABHYAAAPoAAAAAAAAAFAAtgEIAVIB/AKmAxQDggPABBwEpgUoBVwFkAXoBkAG1gcqB6oInAlMChAKpgtSC/IMIQABAAAAGwBVAAcAAAAAAAIAIgAyAHMAAACRC3AAAAAAAAAAEgDeAAEAAAAAAAAANQAAAAEAAAAAAAEADQA1AAEAAAAAAAIABwBCAAEAAAAAAAMADQBJAAEAAAAAAAQADQBWAAEAAAAAAAUACwBjAAEAAAAAAAYADQBuAAEAAAAAAAoAKwB7AAEAAAAAAAsAEwCmAAMAAQQJAAAAagC5AAMAAQQJAAEAGgEjAAMAAQQJAAIADgE9AAMAAQQJAAMAGgFLAAMAAQQJAAQAGgFlAAMAAQQJAAUAFgF/AAMAAQQJAAYAGgGVAAMAAQQJAAoAVgGvAAMAAQQJAAsAJgIFQ29weXJpZ2h0IChDKSAyMDE3IGJ5IG9yaWdpbmFsIGF1dGhvcnMgQCBmb250ZWxsby5jb21zbGltc3RhdC1mb250UmVndWxhcnNsaW1zdGF0LWZvbnRzbGltc3RhdC1mb250VmVyc2lvbiAxLjBzbGltc3RhdC1mb250R2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADcAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAHMAbABpAG0AcwB0AGEAdAAtAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBzAGwAaQBtAHMAdABhAHQALQBmAG8AbgB0AHMAbABpAG0AcwB0AGEAdAAtAGYAbwBuAHQAVgBlAHIAcwBpAG8AbgAgADEALgAwAHMAbABpAG0AcwB0AGEAdAAtAGYAbwBuAHQARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAAIAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGwECAQMBBAEFAQYBBwEIAQkBCgELAQwBDQEOAQ8BEAERARIBEwEUARUBFgEXARgBGQEaARsBHAAFdHJhc2gFY2xvY2sGc2VhcmNoBmNhbmNlbAhkb3dubG9hZAZ1cGxvYWQOZG93bmxvYWQtY2xvdWQMdXBsb2FkLWNsb3VkCmxvY2F0aW9uLTELYXJyb3dzLWN3LTEFbG9naW4GbG9nb3V0CmFuZ2xlLWxlZnQLYW5nbGUtcmlnaHQRYW5nbGUtZG91YmxlLWxlZnQSYW5nbGUtZG91YmxlLXJpZ2h0CWFycm93cy1jdwNkb2MFc3BpbjQEZWRpdAVnYXVnZQlzdG9wd2F0Y2gEZG9jcwl1c2VyLXBsdXMKdXNlci10aW1lcw1taW51cy1jaXJjbGVkAAAAAAABAAH//wAPAAAAAAAAAAAAAAAAAAAAAAAYABgAGAAYA4H/OgOB/zqwACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwgZCCwwFCwBCZasigBCkNFY0VSW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCxAQpDRWNFYWSwKFBYIbEBCkNFY0UgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7ABK1lZI7AAUFhlWVktsAMsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAQsIyEjISBksQViQiCwBiNCsQEKQ0VjsQEKQ7ABYEVjsAMqISCwBkMgiiCKsAErsTAFJbAEJlFYYFAbYVJZWCNZISCwQFNYsAErGyGwQFkjsABQWGVZLbAFLLAHQyuyAAIAQ2BCLbAGLLAHI0IjILAAI0JhsAJiZrABY7ABYLAFKi2wBywgIEUgsAtDY7gEAGIgsABQWLBAYFlmsAFjYESwAWAtsAgssgcLAENFQiohsgABAENgQi2wCSywAEMjRLIAAQBDYEItsAosICBFILABKyOwAEOwBCVgIEWKI2EgZCCwIFBYIbAAG7AwUFiwIBuwQFlZI7AAUFhlWbADJSNhRESwAWAtsAssICBFILABKyOwAEOwBCVgIEWKI2EgZLAkUFiwABuwQFkjsABQWGVZsAMlI2FERLABYC2wDCwgsAAjQrILCgNFWCEbIyFZKiEtsA0ssQICRbBkYUQtsA4ssAFgICCwDENKsABQWCCwDCNCWbANQ0qwAFJYILANI0JZLbAPLCCwEGJmsAFjILgEAGOKI2GwDkNgIIpgILAOI0IjLbAQLEtUWLEEZERZJLANZSN4LbARLEtRWEtTWLEEZERZGyFZJLATZSN4LbASLLEAD0NVWLEPD0OwAWFCsA8rWbAAQ7ACJUKxDAIlQrENAiVCsAEWIyCwAyVQWLEBAENgsAQlQoqKIIojYbAOKiEjsAFhIIojYbAOKiEbsQEAQ2CwAiVCsAIlYbAOKiFZsAxDR7ANQ0dgsAJiILAAUFiwQGBZZrABYyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsQAAEyNEsAFDsAA+sgEBAUNgQi2wEywAsQACRVRYsA8jQiBFsAsjQrAKI7ABYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wFCyxABMrLbAVLLEBEystsBYssQITKy2wFyyxAxMrLbAYLLEEEystsBkssQUTKy2wGiyxBhMrLbAbLLEHEystsBwssQgTKy2wHSyxCRMrLbAeLACwDSuxAAJFVFiwDyNCIEWwCyNCsAojsAFgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAfLLEAHistsCAssQEeKy2wISyxAh4rLbAiLLEDHistsCMssQQeKy2wJCyxBR4rLbAlLLEGHistsCYssQceKy2wJyyxCB4rLbAoLLEJHistsCksIDywAWAtsCosIGCwEGAgQyOwAWBDsAIlYbABYLApKiEtsCsssCorsCoqLbAsLCAgRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOCMgilVYIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgbIVktsC0sALEAAkVUWLABFrAsKrABFTAbIlktsC4sALANK7EAAkVUWLABFrAsKrABFTAbIlktsC8sIDWwAWAtsDAsALABRWO4BABiILAAUFiwQGBZZrABY7ABK7ALQ2O4BABiILAAUFiwQGBZZrABY7ABK7AAFrQAAAAAAEQ+IzixLwEVKi2wMSwgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhOC2wMiwuFzwtsDMsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYbABQ2M4LbA0LLECABYlIC4gR7AAI0KwAiVJiopHI0cjYSBYYhshWbABI0KyMwEBFRQqLbA1LLAAFrAEJbAEJUcjRyNhsAlDK2WKLiMgIDyKOC2wNiywABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyCwCEMgiiNHI0cjYSNGYLAEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYSMgILAEJiNGYTgbI7AIQ0awAiWwCENHI0cjYWAgsARDsAJiILAAUFiwQGBZZrABY2AjILABKyOwBENgsAErsAUlYbAFJbACYiCwAFBYsEBgWWawAWOwBCZhILAEJWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbA3LLAAFiAgILAFJiAuRyNHI2EjPDgtsDgssAAWILAII0IgICBGI0ewASsjYTgtsDkssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbkIAAgAY2MjIFhiGyFZY7gEAGIgsABQWLBAYFlmsAFjYCMuIyAgPIo4IyFZLbA6LLAAFiCwCEMgLkcjRyNhIGCwIGBmsAJiILAAUFiwQGBZZrABYyMgIDyKOC2wOywjIC5GsAIlRlJYIDxZLrErARQrLbA8LCMgLkawAiVGUFggPFkusSsBFCstsD0sIyAuRrACJUZSWCA8WSMgLkawAiVGUFggPFkusSsBFCstsD4ssDUrIyAuRrACJUZSWCA8WS6xKwEUKy2wPyywNiuKICA8sAQjQoo4IyAuRrACJUZSWCA8WS6xKwEUK7AEQy6wKystsEAssAAWsAQlsAQmIC5HI0cjYbAJQysjIDwgLiM4sSsBFCstsEEssQgEJUKwABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyBHsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhsAIlRmE4IyA8IzgbISAgRiNHsAErI2E4IVmxKwEUKy2wQiywNSsusSsBFCstsEMssDYrISMgIDywBCNCIzixKwEUK7AEQy6wKystsEQssAAVIEewACNCsgABARUUEy6wMSotsEUssAAVIEewACNCsgABARUUEy6wMSotsEYssQABFBOwMiotsEcssDQqLbBILLAAFkUjIC4gRoojYTixKwEUKy2wSSywCCNCsEgrLbBKLLIAAEErLbBLLLIAAUErLbBMLLIBAEErLbBNLLIBAUErLbBOLLIAAEIrLbBPLLIAAUIrLbBQLLIBAEIrLbBRLLIBAUIrLbBSLLIAAD4rLbBTLLIAAT4rLbBULLIBAD4rLbBVLLIBAT4rLbBWLLIAAEArLbBXLLIAAUArLbBYLLIBAEArLbBZLLIBAUArLbBaLLIAAEMrLbBbLLIAAUMrLbBcLLIBAEMrLbBdLLIBAUMrLbBeLLIAAD8rLbBfLLIAAT8rLbBgLLIBAD8rLbBhLLIBAT8rLbBiLLA3Ky6xKwEUKy2wYyywNyuwOystsGQssDcrsDwrLbBlLLAAFrA3K7A9Ky2wZiywOCsusSsBFCstsGcssDgrsDsrLbBoLLA4K7A8Ky2waSywOCuwPSstsGossDkrLrErARQrLbBrLLA5K7A7Ky2wbCywOSuwPCstsG0ssDkrsD0rLbBuLLA6Ky6xKwEUKy2wbyywOiuwOystsHAssDorsDwrLbBxLLA6K7A9Ky2wciyzCQQCA0VYIRsjIVlCK7AIZbADJFB4sAEVMC0AS7gAyFJYsQEBjlmwAbkIAAgAY3CxAAVCsgABACqxAAVCswoCAQgqsQAFQrMOAAEIKrEABkK6AsAAAQAJKrEAB0K6AEAAAQAJKrEDAESxJAGIUViwQIhYsQNkRLEmAYhRWLoIgAABBECIY1RYsQMARFlZWVmzDAIBDCq4Af+FsASNsQIARAAA") format("truetype")}[class^="slimstat-font-"]:before,[class*=" slimstat-font-"]:before{color:#616060;font-family:"slimstat";font-style:normal;font-weight:normal;font-size:14px;text-decoration:inherit;width:1em;margin:0 0.2em;text-align:center;font-variant:normal;text-transform:none;line-height:1em}.slimstat-font-trash:before{content:"\e800"}.slimstat-font-clock:before{content:"\e801"}.slimstat-font-search:before{content:"\e802"}.slimstat-font-cancel:before{content:"";background:url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_417_931)'%3E%3Cpath d='M5.89353 4.99459L8.95228 1.93609C9.06613 1.81821 9.12912 1.66034 9.1277 1.49646C9.12628 1.33259 9.06055 1.17583 8.94466 1.05995C8.82878 0.944073 8.67203 0.878342 8.50815 0.876918C8.34428 0.875494 8.18641 0.938491 8.06853 1.05234L5.00978 4.11084L1.95128 1.05234C1.89328 0.994263 1.8244 0.94819 1.74858 0.916756C1.67276 0.885321 1.59148 0.869141 1.5094 0.869141C1.42733 0.869141 1.34605 0.885321 1.27023 0.916756C1.19441 0.94819 1.12553 0.994263 1.06753 1.05234C1.00945 1.11034 0.96338 1.17922 0.931945 1.25504C0.90051 1.33086 0.88433 1.41214 0.88433 1.49421C0.88433 1.57629 0.90051 1.65757 0.931945 1.73339C0.96338 1.80921 1.00945 1.87809 1.06753 1.93609L4.12603 4.99459L1.06053 8.06034C0.972939 8.14767 0.913252 8.25904 0.889038 8.38033C0.864824 8.50162 0.877172 8.62737 0.924517 8.74164C0.971862 8.85591 1.05207 8.95354 1.15498 9.02216C1.25788 9.09078 1.37884 9.1273 1.50253 9.12709C1.66253 9.12709 1.82253 9.06609 1.94453 8.94409L5.01003 5.87834L8.06853 8.93684C8.12652 8.99495 8.19542 9.04103 8.27127 9.07244C8.34712 9.10384 8.42843 9.11995 8.51053 9.11984C8.63411 9.11981 8.75491 9.08315 8.85765 9.01449C8.9604 8.94583 9.04049 8.84825 9.08779 8.73408C9.1351 8.61991 9.14749 8.49429 9.12342 8.37308C9.09934 8.25186 9.03987 8.14051 8.95253 8.05309L5.89353 4.99459Z' fill='%23FF3636'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_417_931'%3E%3Crect width='9' height='9' fill='white' transform='translate(0.509766 0.494141)'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A") no-repeat center center/contain;width:12px;height:12px;display:inline-block;transform:translateY(1px)}.slimstat-font-upload:before{content:"\e805"}.slimstat-font-download-cloud:before{content:"\e806"}.slimstat-font-upload-cloud:before{content:"\e807"}.slimstat-font-location-1:before{content:"\e808"}.slimstat-font-arrows-cw-1:before{content:"\e809"}.slimstat-font-login:before{content:"\e80a"}.slimstat-font-logout:before{content:"\e80b"}.slimstat-font-angle-left:before{content:"\e80c"}.slimstat-font-angle-right:before{content:"\e80d"}.slimstat-font-angle-double-left:before{content:"\e80e"}.slimstat-font-angle-double-right:before{content:"\e80f"}.slimstat-font-arrows-cw:before{content:"\e810"}.slimstat-font-doc:before{content:"\e811"}.slimstat-font-spin4:before{content:"\e812";font-size:26px;overflow:hidden;margin:0}.slimstat-font-edit:before{content:"\e813"}.slimstat-font-gauge:before{content:"\e814"}.slimstat-font-stopwatch:before{content:"\e815"}.slimstat-font-docs:before{content:"\e816"}.slimstat-font-user-plus:before{content:"\e817"}.slimstat-font-user-times:before{content:"\e818"}.slimstat-font-minus-circled:before{content:"\e819"}.animate-spin{-moz-animation:spin 3s infinite linear;-o-animation:spin 3s infinite linear;-webkit-animation:spin 3s infinite linear;animation:spin 3s infinite linear;display:inline-block;line-height:1em}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-o-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-ms-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:280px;min-width:50px;font-size:1em;line-height:1.5em;direction:ltr;box-shadow:none;border-radius:5px;padding:0}.qtip-content,.qtip-titlebar{position:relative;overflow:hidden}.qtip-content{padding:5px 9px;text-align:left;word-wrap:break-word}.qtip-titlebar{padding:5px 35px 5px 10px;border-radius:0 0 1px;font-weight:700}.qtip-titlebar+.qtip-content{border-top-width:0 !important}.qtip-close{position:absolute;right:-9px;top:-9px;z-index:11;cursor:pointer;outline:0;border:1px solid transparent}.qtip-titlebar .qtip-close{right:4px;top:50%;margin-top:-9px}* html .qtip-titlebar .qtip-close{top:16px}.qtip-icon .ui-icon,.qtip-titlebar .ui-icon{display:block;text-indent:-1000em;direction:ltr}.qtip-icon,.qtip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;text-decoration:none}.qtip-icon .ui-icon{width:18px;height:14px;line-height:1.5em;text-align:center;text-indent:0;font:normal 700 1em Tahoma, sans-serif;color:inherit;background:-100em -100em no-repeat}.qtip-default{background-color:#2b2b2b;font-style:italic;padding-top:12px;padding-right:16px;padding-bottom:12px;padding-left:16px;border-radius:12px;color:#9ba1a6;font-weight:600;font-size:12px;line-height:20px;letter-spacing:0px;font-style:normal;text-decoration:none !important;transform:translateX(9px) translateY(-1px)}.qtip-default strong{color:#fff;font-weight:700;font-size:14px;line-height:20px;letter-spacing:0px;font-style:normal;text-decoration:none !important}.qtip-default .qtip-titlebar{background-color:#ffef93}.qtip-default .qtip-icon{border-color:#ccc;background:#f1f1f1;color:#777}.qtip-default .qtip-titlebar .qtip-close{border-color:#aaa;color:#111}.qtip .qtip-tip{margin:0 auto;overflow:hidden;z-index:10}.qtip .qtip-tip,x:-o-prefocus{visibility:hidden}.qtip .qtip-tip,.qtip .qtip-tip .qtip-vml,.qtip .qtip-tip canvas{position:absolute;color:#2b2b2b !important;background:#2b2b2b !important;border:0 dashed transparent;background-color:#2b2b2b !important;width:12px !important;height:12px !important;transform:rotate(135deg) translateX(0px) translateY(1px);line-height:unset !important;border-top-right-radius:4px;display:inline-block}.qtip .qtip-tip canvas{top:0;left:0;display:none !important}.qtip .qtip-tip .qtip-vml{behavior:url(#default#VML);display:inline-block;visibility:visible}/*! jQuery UI - v1.10.3 | https://jqueryui.com */#ui-datepicker-div{display:none}.ui-datepicker{background-color:#fff;width:17em;padding:8px;border-radius:8px;box-shadow:5px 0px 40px rgba(0,0,0,0.2);border:none !important;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:0.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:4px;width:26px;height:26px;cursor:pointer}.ui-datepicker .ui-datepicker-prev:hover,.ui-datepicker .ui-datepicker-next:hover{background-color:white}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;top:50%;transform:translate(-50%, -50%);font-size:15px;font-weight:600;color:#616060;transition:0.3s all ease-out}.ui-datepicker .ui-datepicker-prev span:hover,.ui-datepicker .ui-datepicker-next span:hover{color:#e8294c}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:0.9em;border-collapse:collapse;margin:0 0 0.4em}.ui-datepicker th{padding:0.7em 0.3em;text-align:center;font-weight:700;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:0.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:0.7em 0 0;padding:0 0.2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:0.5em 0.2em 0.4em;cursor:pointer;padding:0.2em 0.6em 0.3em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto 0.4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}/*! bootstrap-switch - v3.3.4 | https://www.bootstrap-switch.org */.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid #ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;-o-transition:border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;transition:border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block !important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary{color:#fff;background:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#e8294c}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch input[type="radio"],.bootstrap-switch input[type="checkbox"]{position:absolute !important;top:0;left:0;margin:0;z-index:-1;opacity:0 !important;filter:alpha(opacity=0)}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.3333333}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-indeterminate,.bootstrap-switch.bootstrap-switch-readonly{cursor:default !important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label{opacity:0.5;filter:alpha(opacity=50);cursor:default !important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left 0.5s;-o-transition:margin-left 0.5s;transition:margin-left 0.5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:0 3px 3px 0}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:3px 0 0 3px}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px}.tag-editor{list-style-type:none;padding:0 5px 0 0;margin:0;overflow:hidden;border:1px solid #eee;cursor:text;font:normal 14px sans-serif;color:#555;background:#fff;line-height:20px}.tag-editor li{display:block;float:left;overflow:hidden;margin:3px 0}.tag-editor div{float:left;padding:0 4px}.tag-editor .placeholder{padding:0 8px;color:#bbb}.tag-editor .tag-editor-spacer{padding:0;width:8px;overflow:hidden;color:transparent;background:none}.tag-editor input{vertical-align:inherit;border:0;outline:none;padding:0;margin:0;cursor:text;font-family:inherit;font-weight:inherit;font-size:inherit;font-style:inherit;box-shadow:none;background:none;color:#151515}.tag-editor-hidden-src{position:absolute !important;left:-99999px}.tag-editor ::-ms-clear{display:none}.tag-editor .tag-editor-tag{padding-left:5px;color:#46799b;background:#e0eaf1;white-space:nowrap;overflow:hidden;cursor:pointer;border-radius:2px 0 0 2px}.tag-editor .tag-editor-delete{background:#e0eaf1;cursor:pointer;border-radius:0 2px 2px 0;padding-left:3px;padding-right:4px}.tag-editor .tag-editor-delete i{line-height:18px;display:inline-block}.tag-editor .tag-editor-delete i:before{font-size:16px;color:#8ba7ba;content:"×";font-style:normal}.tag-editor .tag-editor-delete:hover i:before{color:#d65454}.tag-editor .tag-editor-tag.active+.tag-editor-delete,.tag-editor .tag-editor-tag.active+.tag-editor-delete i{visibility:hidden;cursor:text}.tag-editor .tag-editor-tag.active{background:none !important}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default;font-size:14px}.ui-front{z-index:9999}.ui-menu{list-style:none;padding:1px;margin:0;display:block;outline:none}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px 0.4em;line-height:1.4;min-height:0}.ui-widget-content{border:1px solid #bbb;background:#fff;color:#555}.ui-widget-content a{color:#46799b}.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{background:#e0eaf1}.ui-helper-hidden-accessible{display:none}.backdrop-container{position:relative;background-color:#f5f6fa}@media (min-width: 780px){.backdrop-container{margin-left:-20px;margin-top:-20px;padding-left:20px;padding-top:20px}}.slimstat-filter-link .avatar{border-radius:50px;overflow:hidden;display:inline-block;transform:translateY(3px);margin-right:2px;border:solid 1px #fff}.wrap.slimstat-config,.wrap.slimstat{box-sizing:border-box;overflow:hidden}.wrap.slimstat-config *,.wrap.slimstat *{box-sizing:border-box;text-decoration:none}.wrap.slimstat-config .upgrade-pro-background,.wrap.slimstat .upgrade-pro-background{width:100%}.wrap.slimstat-config select:focus,.wrap.slimstat-config input:focus,.wrap.slimstat-config textarea:focus,.wrap.slimstat select:focus,.wrap.slimstat input:focus,.wrap.slimstat textarea:focus{outline:none;box-shadow:none;border-color:#e8294c !important}.wrap.slimstat-config .button-secondary,.wrap.slimstat .button-secondary{background-color:white;border-radius:50px;border:1px solid #e8294c;color:#e8294c;padding:0px 12px;height:auto}.wrap.slimstat-config .button-primary,.wrap.slimstat .button-primary{background-color:#e8294c;border-radius:50px;border:none;color:white;padding:0px 12px;height:auto}.wrap.slimstat-config #datepicker-backdrop,.wrap.slimstat #datepicker-backdrop{width:100vw;height:100vh;top:0;left:0;position:fixed;z-index:2;display:none}.wrap.slimstat-config #slimstat-date-filters a.slimstat-filter-link,.wrap.slimstat #slimstat-date-filters a.slimstat-filter-link{background-color:#f8f8f8;display:block;float:left;margin:0 5px 5px 0;padding:7px 5px;width:123px;border-radius:50px;text-align:center;color:#151515;transition:0.3s all ease-out}.wrap.slimstat-config #slimstat-date-filters a.slimstat-filter-link:hover,.wrap.slimstat #slimstat-date-filters a.slimstat-filter-link:hover{background-color:#f3f3f3}.wrap.slimstat-config #slimstat-date-filters input,.wrap.slimstat-config #slimstat-date-filters select,.wrap.slimstat #slimstat-date-filters input,.wrap.slimstat #slimstat-date-filters select{border:2px solid #f3f3f3;border-radius:50px}.wrap.slimstat-config #slimstat-date-filters input[type="submit"],.wrap.slimstat #slimstat-date-filters input[type="submit"]{background-color:#e8294c;border:none;border-radius:50px;transition:0.3s all ease-out}.wrap.slimstat-config #slimstat-date-filters input[type="submit"]:hover,.wrap.slimstat #slimstat-date-filters input[type="submit"]:hover{background-color:black}.wrap.slimstat-config .button-primary.slimstat-settings-button,.wrap.slimstat .button-primary.slimstat-settings-button{background-color:#e8294c;color:white;border-radius:50px;border:none;transition:0.3s all ease-out;padding-left:12px;padding-right:12px}.wrap.slimstat-config .button-primary.slimstat-settings-button:hover,.wrap.slimstat .button-primary.slimstat-settings-button:hover{background-color:black}.wrap.slimstat-config h2,.wrap.slimstat h2{color:black;font-weight:600;font-size:24px}.wrap.slimstat-config .nav-tabs,.wrap.slimstat .nav-tabs{display:flex;background-color:white;border-radius:5px;overflow-y:hidden}.wrap.slimstat-config .nav-tabs .nav-tab,.wrap.slimstat .nav-tabs .nav-tab{transition:0.3s all ease-out;border:none;background-color:transparent;padding:0}.wrap.slimstat-config .nav-tabs .nav-tab a,.wrap.slimstat .nav-tabs .nav-tab a{color:#151515;font-size:14px;padding:15px 15px}.wrap.slimstat-config .nav-tabs .nav-tab a:focus,.wrap.slimstat .nav-tabs .nav-tab a:focus{outline:none;box-shadow:none}.wrap.slimstat-config .nav-tabs .nav-tab a:hover,.wrap.slimstat .nav-tabs .nav-tab a:hover{color:#e8294c}.wrap.slimstat-config .nav-tabs .nav-tab.nav-tab-active,.wrap.slimstat .nav-tabs .nav-tab.nav-tab-active{border-bottom:5px solid #e8294c}.wrap.slimstat-config .nav-tabs .nav-tab.nav-tab-active a,.wrap.slimstat .nav-tabs .nav-tab.nav-tab-active a{color:#e8294c}@media (max-width: 900px){.wrap.slimstat-config .nav-tabs,.wrap.slimstat .nav-tabs{overflow-x:scroll}}.wrap.slimstat-config .form-table,.wrap.slimstat .form-table{border:none;margin-top:25px;border-radius:10px}.wrap.slimstat-config .form-table tr,.wrap.slimstat .form-table tr{border-bottom:1px solid #eee}.wrap.slimstat-config .form-table tr:first-of-type .slimstat-options-section-header,.wrap.slimstat .form-table tr:first-of-type .slimstat-options-section-header{border-radius:10px 10px 0 0}.wrap.slimstat-config .form-table tr.alternate,.wrap.slimstat .form-table tr.alternate{background-color:white}.wrap.slimstat-config .form-table tr th,.wrap.slimstat .form-table tr th{padding:25px 20px;color:#151515}.wrap.slimstat-config .form-table tr td,.wrap.slimstat .form-table tr td{padding:25px 20px;color:#151515}.wrap.slimstat-config .form-table tr td input,.wrap.slimstat .form-table tr td input{border:2px solid #f3f3f3;border-radius:50px}.wrap.slimstat-config .form-table tr td textarea,.wrap.slimstat .form-table tr td textarea{border:2px solid #f3f3f3;border-radius:10px}.wrap.slimstat-config .form-table tr td select,.wrap.slimstat .form-table tr td select{border:2px solid #f3f3f3;border-radius:50px}.wrap.slimstat-config .form-table tr td .button-primary,.wrap.slimstat .form-table tr td .button-primary{border:1px solid #e8294c;color:#e8294c;background-color:white;border-radius:50px}.wrap.slimstat-config .form-table tr td .tag-editor,.wrap.slimstat .form-table tr td .tag-editor{border-radius:6px;padding:6px 4px}.wrap.slimstat-config .form-table tr td .tag-editor .tag-editor-tag,.wrap.slimstat .form-table tr td .tag-editor .tag-editor-tag{color:#e8294c;background-color:#fff4f5}.wrap.slimstat-config .form-table tr td .tag-editor .tag-editor-delete,.wrap.slimstat .form-table tr td .tag-editor .tag-editor-delete{color:#e8294c;background-color:#fff4f5}.wrap.slimstat-config .form-table tr td .tag-editor .tag-editor-delete i::before,.wrap.slimstat .form-table tr td .tag-editor .tag-editor-delete i::before{color:#e8294c}.wrap.slimstat-config .form-table tr td .bootstrap-switch-wrapper,.wrap.slimstat .form-table tr td .bootstrap-switch-wrapper{border:2px solid #f3f3f3;border-radius:50px}.wrap.slimstat-config .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-on,.wrap.slimstat .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-on{border-radius:50px;padding:5px 0px}.wrap.slimstat-config .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-off,.wrap.slimstat .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-off{border-radius:50px;padding:5px 0px;background-color:#f8f8f8;color:#151515}.wrap.slimstat-config .form-table tr .description,.wrap.slimstat .form-table tr .description{color:#616060;margin-top:7px;display:block}.wrap.slimstat-config .slimstat-options-section-header,.wrap.slimstat .slimstat-options-section-header{background-color:#f8f8f8;padding:20px 20px;font-weight:500;color:#151515;font-size:16px}.wrap.slimstat-config #slimstat-filters,.wrap.slimstat #slimstat-filters{display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;margin-bottom:5px}.wrap.slimstat-config #slimstat-filters .form-field,.wrap.slimstat #slimstat-filters .form-field{position:relative}.wrap.slimstat-config #slimstat-filters select,.wrap.slimstat #slimstat-filters select{border:1px solid #eee;border-radius:5px;display:block;width:100%;margin:0}.wrap.slimstat-config #slimstat-filters input,.wrap.slimstat #slimstat-filters input{border:1px solid #eee;border-radius:5px;display:block;width:100%;margin:0;max-width:95%}.wrap.slimstat-config #slimstat-filters input[type="submit"],.wrap.slimstat #slimstat-filters input[type="submit"]{border:1px solid #e8294c;background-color:white;color:#e8294c;width:auto}.rtl .wrap.slimstat-config #slimstat-filters input[type="submit"],.rtl .wrap.slimstat #slimstat-filters input[type="submit"]{margin-left:0}@media (max-width: 400px){.wrap.slimstat-config #slimstat-filters,.wrap.slimstat #slimstat-filters{margin-bottom:12px}.wrap.slimstat-config #slimstat-filters .form-field,.wrap.slimstat #slimstat-filters .form-field{flex:0 0 50%;margin-bottom:10px}}.wrap.slimstat-config .meta-box-sortables form,.wrap.slimstat .meta-box-sortables form{display:none}.wrap.slimstat-config .meta-box-sortables .postbox,.wrap.slimstat .meta-box-sortables .postbox{border:solid 1px #f0f0f0;border-radius:10px;margin-right:0.5%}.rtl .wrap.slimstat-config .meta-box-sortables .postbox,.rtl .wrap.slimstat .meta-box-sortables .postbox{margin-right:0;margin-left:0.5%}.wrap.slimstat-config .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner,.wrap.slimstat .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner{display:inline-block;float:none;margin-right:3px;cursor:help}.wrap.slimstat-config .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner::before,.wrap.slimstat .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner::before{color:#aaaaaa;transform:scaleX(-1);display:block}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons{padding:15px 10px 0 10px;display:flex;align-items:center;justify-content:flex-end;float:right}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons .dashicons,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons .dashicons{width:auto;height:auto}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a:focus,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a:focus{box-shadow:none}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.refresh,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.refresh{padding:5px 5px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.refresh::before,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.refresh::before{transform:translateY(-1px);display:block;color:#616060;transition:0.3s all ease-out}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download{padding:5px 5px;border-radius:5px;font-weight:700;font-size:12px;line-height:100%;letter-spacing:0px;color:#676e74;transition:0.3s all ease-out;cursor:pointer;position:relative;top:0px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover{color:#202224}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover span.dashicons::before,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover span.dashicons::before{color:#202224}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before{color:#676e74;font-size:14px;margin-right:2px;display:block;transform:translateY(1px);transition:0.3s all ease-out}.rtl .wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons,.rtl .wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons{float:left}.rtl .wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before,.rtl .wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before{margin-right:0px;margin-left:3px}.wrap.slimstat-config .meta-box-sortables .postbox h3,.wrap.slimstat .meta-box-sortables .postbox h3{background-color:#ffffff;color:#202224;padding:18px 15px;border:none;font-size:16px;font-weight:bold;letter-spacing:-0.43px}.wrap.slimstat-config .meta-box-sortables .postbox h3 .header-tooltip,.wrap.slimstat .meta-box-sortables .postbox h3 .header-tooltip{margin:0px 5px;display:inline-block;transform:translateY(3px)}.wrap.slimstat-config .meta-box-sortables .postbox h3 .header-tooltip:hover svg path,.wrap.slimstat .meta-box-sortables .postbox h3 .header-tooltip:hover svg path{fill:#202224}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-browser-icon,.wrap.slimstat-config .meta-box-sortables .postbox span.slimstat-flag-container,.wrap.slimstat .meta-box-sortables .postbox .slimstat-browser-icon,.wrap.slimstat .meta-box-sortables .postbox span.slimstat-flag-container{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-author-link img,.wrap.slimstat .meta-box-sortables .postbox .slimstat-author-link img{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap,.wrap.slimstat .meta-box-sortables .postbox .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap{z-index:0 !important;position:absolute;display:block;width:calc(100% - 30px);height:100%;top:0;left:0;margin:0px 15px;box-sizing:border-box}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-tooltip-trigger *,.wrap.slimstat .meta-box-sortables .postbox .slimstat-tooltip-trigger *{z-index:2 !important;position:relative}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-tooltip-trigger a,.wrap.slimstat .meta-box-sortables .postbox .slimstat-tooltip-trigger a{max-width:80%;display:inline-block;color:#202224;font-weight:600;vertical-align:top;font-size:12px}.wrap.slimstat-config p.slimstat-tooltip-trigger,.wrap.slimstat p.slimstat-tooltip-trigger{border:none !important;margin-bottom:8px}.wrap.slimstat-config .pagination,.wrap.slimstat .pagination{color:#6f7478;position:absolute;left:0;bottom:0;background-color:#fff;z-index:2;width:100%;padding:10px 15px}.wrap.slimstat-config a.refresh.slimstat-font-angle-double-right,.wrap.slimstat-config a.refresh.slimstat-font-angle-right,.wrap.slimstat-config a.refresh.slimstat-font-angle-double-left,.wrap.slimstat-config a.refresh.slimstat-font-angle-left,.wrap.slimstat a.refresh.slimstat-font-angle-double-right,.wrap.slimstat a.refresh.slimstat-font-angle-right,.wrap.slimstat a.refresh.slimstat-font-angle-double-left,.wrap.slimstat a.refresh.slimstat-font-angle-left{width:24px;border:solid 1px #dadce0;height:24px;padding:0;border-radius:50%;text-align:center;vertical-align:middle}.wrap.slimstat-config a.refresh.slimstat-font-angle-double-right:before,.wrap.slimstat-config a.refresh.slimstat-font-angle-right:before,.wrap.slimstat-config a.refresh.slimstat-font-angle-double-left:before,.wrap.slimstat-config a.refresh.slimstat-font-angle-left:before,.wrap.slimstat a.refresh.slimstat-font-angle-double-right:before,.wrap.slimstat a.refresh.slimstat-font-angle-right:before,.wrap.slimstat a.refresh.slimstat-font-angle-double-left:before,.wrap.slimstat a.refresh.slimstat-font-angle-left:before{color:#676e74 !important;font-size:13px}.wrap.slimstat-config span.slimstat-tooltip-bar,.wrap.slimstat span.slimstat-tooltip-bar{display:block;height:100%;max-width:100% !important;position:absolute !important;top:0;left:0;background-color:var(--box-bar-color);border-radius:6px}.wrap.slimstat-config .slimScrollDiv p,.wrap.slimstat .slimScrollDiv p{padding:10px 25px}.wrap.slimstat-config .slimScrollDiv p.header,.wrap.slimstat .slimScrollDiv p.header{border:none}.wrap.slimstat-config .slimScrollDiv code,.wrap.slimstat .slimScrollDiv code{border-radius:3px;background-color:white;padding:3px 7px}.wrap.slimstat table.widefat{border:none}.wrap.slimstat table.widefat tbody tr:nth-child(even){background-color:#f8f8f8}.wrap.slimstat table.widefat tbody tr td{vertical-align:middle}.wrap.slimstat table.widefat thead{background-color:#fff4f5}.wrap.slimstat table.widefat thead th{white-space:nowrap;border:none;border-top:1px solid #e8294c;border-bottom:1px solid #e8294c;color:black;padding:8px 10px;vertical-align:middle}.wrap.slimstat table.widefat thead th a{color:#e8294c}.slimstat-header{padding:30px 25px;margin-bottom:20px;background-color:#2b2b2b;display:flex;align-items:center;justify-content:flex-start;position:relative;margin-left:-20px;box-sizing:border-box}.slimstat-header .logo{width:180px;height:auto}.slimstat-header .vr-line{background-color:#9a9a9a;margin:0 40px;display:block;width:1px;height:40px}.slimstat-header .go-pro{text-align:left}.slimstat-header .go-pro a{display:flex;align-items:center;justify-content:flex-start;margin-bottom:3px;color:white;text-decoration:none;font-size:15px;font-weight:400;cursor:pointer}.slimstat-header .go-pro a .icon{background:url("../images/white-right-chevron.png") no-repeat center center/contain;width:10px;height:10px;margin-left:5px;display:block}.slimstat-header .go-pro p{text-align:left;font-weight:300;color:white;margin:0}.slimstat-header .pro-badge{display:flex;align-items:center;justify-content:flex-start;position:absolute;right:20px;bottom:20px;color:white}.slimstat-header .pro-badge p{margin:0}.slimstat-header .pro-badge .icon{background:url("../images/pro-badge.png") no-repeat center center/contain;width:16px;height:16px;margin-right:7px;display:block}.rtl .slimstat-header{margin-right:-20px;margin-left:0}.rtl .slimstat-header .pro-badge{right:auto;left:20px}.rtl .slimstat-header .pro-badge .icon{margin-left:7px;margin-right:0}.rtl .slimstat-header .go-pro a .icon{background:url(../images/white-right-chevron.png) no-repeat center center/contain;margin-left:0px;margin-right:5px;transform:scaleX(-1)}.rtl .slimstat-header .go-pro p{text-align:right}@media (max-width: 500px){.slimstat-header{display:block}.slimstat-header .vr-line{margin:20px 0;width:100%;height:1px}.slimstat-header .go-pro{margin-top:20px}.slimstat-header .pro-badge{right:10px}.rtl .slimstat-header .pro-badge{left:10px;right:auto}}.slimstat-layout{position:relative}.slimstat-layout .postbox-container{border:solid 1px #f0f0f0}.slimstat-layout .postbox-container:has(.ui-sortable-placeholder){border:dashed 1px #ff8080}.slimstat-layout .postbox-container .meta-box-sortables,.slimstat-layout .postbox-container h2{background-color:#fff !important;color:#202224;padding:18px 15px;font-size:16px !important;font-weight:bold !important;letter-spacing:-0.43px}.slimstat-layout .postbox{border-radius:8px !important;box-shadow:none !important;background-color:#fff}.slimstat-layout .postbox h3{font-weight:400 !important;padding:12px 15px !important;background-color:#f1f1f1 !important;font-size:14px !important}#dashboard-widgets-wrap .postbox .slimstat-tooltip-trigger.corner{display:inline-block;float:none;margin-right:3px;cursor:help}#dashboard-widgets-wrap .postbox .slimstat-tooltip-trigger.corner::before{color:#aaaaaa;display:block;transform:scaleX(-1)}.slimstat-pro-modal-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(3px);background-color:rgba(255,255,255,0.5);z-index:20;display:none}.slimstat-pro-modal{position:fixed;z-index:30;transform:translateX(-50%);background-color:white;border-radius:25px;box-shadow:0px 4px 80px rgba(0,0,0,0.15);padding:40px 4%;text-align:center;box-sizing:border-box;max-width:720px;margin-top:100px;left:50%;transform:translate(-50%, 0);top:45%;transform:translateX(-50%) translateY(-50%)}.slimstat-pro-modal *{margin:0;padding:0;box-sizing:border-box}.slimstat-pro-modal #slimstat-pro-modal-close{position:absolute;cursor:pointer;top:30px;right:10%;width:30px;height:30px;opacity:0.3;transition:0.3s all ease-out;background:url("data:image/svg+xml,%3Csvg width='33' height='32' viewBox='0 0 33 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.6172 31.1641C24.9015 31.1641 31.6172 24.4483 31.6172 16.1641C31.6172 7.87979 24.9015 1.16406 16.6172 1.16406C8.33292 1.16406 1.61719 7.87979 1.61719 16.1641C1.61719 24.4483 8.33292 31.1641 16.6172 31.1641Z' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21.1172 11.6641L12.1172 20.6641' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12.1172 11.6641L21.1172 20.6641' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center/contain}.slimstat-pro-modal #slimstat-pro-modal-close:hover{opacity:1}.slimstat-pro-modal h2 .subtitle{font-size:18px;margin-bottom:12px;color:#151515;display:block}.slimstat-pro-modal h2 .title{color:#e8294c;font-size:22px;font-weight:700;margin-bottom:25px;display:block}.slimstat-pro-modal .description{color:#616060;font-weight:400;font-size:16px;margin-bottom:25px}.slimstat-pro-modal .scroller{max-height:45vh;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#333333 #f5f5f5;scrollbar-face-color:#f5f5f5;scrollbar-arrow-color:#333333;scrollbar-track-color:#f5f5f5;scrollbar-shadow-color:#333333;scrollbar-highlight-color:#333333;scrollbar-3dlight-color:#333333;scrollbar-darkshadow-color:#333333}.slimstat-pro-modal .scroller::-webkit-scrollbar{width:5px}.slimstat-pro-modal .scroller::-webkit-scrollbar-thumb{background-color:#333333;border-radius:10px}.slimstat-pro-modal .scroller::-webkit-scrollbar-track{background-color:#f5f5f5}.slimstat-pro-modal .features-flex-box{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap}.slimstat-pro-modal .features-flex-box .feature-item{flex:0 0 45%;border-bottom:1px solid #f3f3f3;padding:15px 0;display:flex;align-items:center;justify-content:space-between}.slimstat-pro-modal .features-flex-box .feature-item h6{color:#151515;font-weight:500;font-size:16px}.slimstat-pro-modal .features-flex-box .feature-item h6 .icon{transform:translateY(6px);width:20px;height:20px;margin-right:7px;display:inline-block;background:url("data:image/svg+xml,%3Csvg width='18' height='17' viewBox='0 0 18 17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.4463 7.90039V8.57696C16.4454 10.1628 15.9319 11.7058 14.9823 12.976C14.0328 14.2461 12.6982 15.1753 11.1774 15.625C9.65665 16.0746 8.03129 16.0206 6.54373 15.471C5.05617 14.9215 3.78612 13.9057 2.92298 12.5754C2.05985 11.245 1.64988 9.67128 1.75422 8.08889C1.85857 6.50649 2.47163 5.00021 3.50198 3.79471C4.53233 2.5892 5.92476 1.74905 7.4716 1.39956C9.01844 1.05007 10.6368 1.20997 12.0854 1.8554' stroke='%23E8294C' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M16.445 2.69531L9.09097 10.0567L6.88477 7.85047' stroke='%23E8294C' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center/contain}.slimstat-pro-modal .features-flex-box .feature-item .more-info-icon{display:block;width:20px;height:20px;margin-left:3px;background:url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.20996 15.4014C12.1434 15.4014 15.332 12.2127 15.332 8.2793C15.332 4.34589 12.1434 1.15723 8.20996 1.15723C4.27655 1.15723 1.08789 4.34589 1.08789 8.2793C1.08789 12.2127 4.27655 15.4014 8.20996 15.4014Z' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M6.13672 6.14245C6.30416 5.66645 6.63466 5.26508 7.06968 5.00942C7.5047 4.75376 8.01616 4.6603 8.51348 4.7456C9.0108 4.83091 9.46188 5.08946 9.78683 5.47548C10.1118 5.8615 10.2896 6.35007 10.2889 6.85465C10.2889 8.27907 8.15226 8.99127 8.15226 8.99127' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8.21094 11.8398H8.21769' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center/contain}.slimstat-pro-modal .go-pro-button{display:inline-block;padding:15px 10px;width:250px;border-radius:10px;font-size:16px;font-weight:500;color:white;text-align:center;background-color:#e8294c;text-decoration:none;margin-top:35px;transition:0.3s all ease-out}.slimstat-pro-modal .go-pro-button:hover{background-color:black}.rtl .slimstat-pro-modal .features-flex-box .feature-item h6 .icon{margin-left:7px;margin-right:0}.rtl .slimstat-pro-modal #slimstat-pro-modal-close{left:10%;right:auto}@media (max-width: 780px){.slimstat-pro-modal{border-radius:15px;padding:20px 20px;width:95%}.slimstat-pro-modal #slimstat-pro-modal-close{top:20px;right:20px}.slimstat-pro-modal h2 .subtitle{font-size:15px}.slimstat-pro-modal h2 .title{font-size:18px}.slimstat-pro-modal .features-flex-box .feature-item{flex:0 0 100%}.slimstat-pro-modal .features-flex-box .feature-item h6 .icon{transform:translateY(4px)}} +.slimstat-postbox-custom-legend{display:flex;justify-content:flex-start;margin-top:20px;gap:40px;font-size:14px}#chartjs-tooltip{transition:.3s all ease-in-out;pointer-events:none !important;background:#fff;z-index:9999;font:13px "Open Sans",sans-serif;border:solid 1px #e0e0e0;border-radius:12px;padding:10px 18px;margin:0;min-width:180px;max-width:320px;opacity:.98;filter:drop-shadow(0 2px 8px rgba(60, 60, 90, 0.1))}#chartjs-tooltip:hover{opacity:.3}#chartjs-tooltip .slimstat-postbox-chart--item[data-index].active .tooltip-item-content{color:#000 !important}@media(max-width: 600px){#chartjs-tooltip{min-width:120px;max-width:98vw;font-size:12px;padding:7px 8px}}span.slimstat-toggle-prev-datasets{position:absolute;right:110px;max-width:80%;display:inline-block;color:#56585a;font-weight:600;vertical-align:top;font-size:12px;cursor:pointer;font-style:oblique;opacity:.7}span.slimstat-toggle-prev-datasets.active{opacity:1}.slimstat-chart-controls{position:absolute;right:20px;text-align:right}.slimstat-chart-controls select{top:-3px;position:relative;display:inline-block;width:84px;min-height:11px !important;line-height:22px !important;border:none;text-decoration:underline dashed 1px}.slimstat-postbox-chart--items{display:flex;column-gap:25px;row-gap:20px;margin-bottom:5px;flex-wrap:wrap;padding:0px 0px 0px 20px;align-items:baseline}@media(max-width: 550px){.slimstat-postbox-chart--items{column-gap:40px}}.slimstat-postbox-chart--item{display:flex;flex-direction:row;gap:8px;align-items:center;cursor:pointer}.slimstat-postbox-chart--item:not(:last-child){margin-bottom:5px}.slimstat-postbox-chart--item.slimstat-postbox-chart--item-hidden{opacity:.5}.slimstat-postbox-chart--item>span{font-size:13px;font-style:italic;font-weight:400;line-height:15.23px;color:#56585a;text-transform:capitalize}.slimstat-postbox-chart--item>span.slimstat-postbox-chart--item-label{font-weight:500;font-size:12px;font-style:italic}.slimstat-postbox-chart--item .slimstat-postbox-chart--item--color{display:inline-flex;width:15px;border-radius:2px;height:5px;margin-right:4px}.slimstat-postbox-chart--item .slimstat-postbox-chart--item--prev{font-size:9px;color:#616060;font-weight:bold;letter-spacing:-0.5px;margin:0 3px;top:-1px;position:relative}.slimstat-postbox-chart--item>div{display:flex;gap:16px;align-items:center;cursor:pointer}body.index-php #dashboard-widgets-wrap .postbox .slimstat-tooltip-trigger.corner.slimstat-toggle-prev-datasets{display:block;float:none;cursor:help;width:120px !important;max-width:120px !important;position:relative;left:unset;right:unset;text-align:left;min-width:200px !important;margin:5px 20px;margin-bottom:10px}body.index-php .slimstat-chart-wrap{max-width:100% !important;width:100%;aspect-ratio:16/7;height:auto}body.index-php .inside:has(.slimstat-postbox-chart--canvas){height:auto !important;min-height:180px;max-height:none;margin-bottom:20px !important}body.index-php .slimstat-postbox-chart--canvas{width:100% !important;max-width:100%;min-width:100px;aspect-ratio:16/7;height:auto !important;min-height:120px;max-height:60vw;border-radius:7px;margin:0 auto 8px auto;box-shadow:0 1px 4px rgba(60,60,90,.04);display:block}body.index-php .slimstat-postbox-custom-legend,body.index-php .slimstat-postbox-chart--items{flex-direction:row;flex-wrap:wrap;gap:12px;column-gap:12px;row-gap:8px;padding-left:0;overflow-x:auto;font-size:13px;width:100%;box-sizing:border-box;justify-content:start;margin:5px 20px}body.index-php .slimstat-postbox-chart--item{padding:3px 7px;font-size:12px;border-radius:5px;margin-bottom:2px}body.index-php .slimstat-chart-controls{right:10px;position:absolute;display:inline-block;width:80px;text-align:left;left:unset}body.index-php .slimstat-chart-controls select{width:90px;font-size:12px;padding:1px 4px;border-radius:5px}@media(max-width: 900px){body.index-php .slimstat-postbox-chart--canvas{aspect-ratio:16/10;min-height:90px;max-height:50vw}}@media(max-width: 600px){body.index-php .slimstat-postbox-custom-legend,body.index-php .slimstat-postbox-chart--items{flex-direction:column;gap:8px;column-gap:8px;row-gap:6px;font-size:12px;width:100%}body.index-php .slimstat-postbox-chart--canvas{min-width:80px;aspect-ratio:16/12;min-height:60px;max-height:40vw;border-radius:5px}body.index-php .slimstat-chart-wrap{aspect-ratio:16/12;min-height:60px}}@media(max-width: 400px){body.index-php .slimstat-postbox-chart--canvas{min-width:60px;min-height:40px;max-height:30vw}body.index-php .slimstat-chart-wrap{min-height:40px}}[dir=rtl] .slimstat-postbox-custom-legend,.rtl .slimstat-postbox-custom-legend{flex-direction:row-reverse;justify-content:flex-end;gap:40px;text-align:right}[dir=rtl] .slimstat-chart-controls,.rtl .slimstat-chart-controls{right:auto;left:20px;text-align:left}[dir=rtl] #chartjs-tooltip,.rtl #chartjs-tooltip{direction:rtl;text-align:right;font-family:"Open Sans",sans-serif !important}[dir=rtl] .slimstat-postbox-custom-legend,.rtl .slimstat-postbox-custom-legend,[dir=rtl] .slimstat-postbox-chart--items,.rtl .slimstat-postbox-chart--items{direction:rtl;text-align:right;font-family:"Open Sans",sans-serif !important}[dir=rtl] .slimstat-postbox-chart--item,.rtl .slimstat-postbox-chart--item{direction:rtl;text-align:right;font-family:"Open Sans",sans-serif !important}[dir=rtl] .tooltip-item-title,.rtl .tooltip-item-title,[dir=rtl] .tooltip-item-content,.rtl .tooltip-item-content{direction:rtl;text-align:right;font-family:"Open Sans",sans-serif !important}[id^=slim_] .inside:has(.slimstat-postbox-chart--canvas){margin-bottom:20px !important;height:265px !important}[id^=slim_] .inside:has(.slimstat-postbox-chart--canvas) .slimstat-postbox-chart--canvas{height:240px !important;width:100% !important;min-height:240px !important;max-height:240px !important}.chart-placeholder{height:280px;overflow:hidden}[dir=rtl] #chartjs-tooltip table,.rtl #chartjs-tooltip table{direction:rtl;text-align:right}[dir=rtl] #chartjs-tooltip tr.slimstat-postbox-chart--item td,.rtl #chartjs-tooltip tr.slimstat-postbox-chart--item td{padding-right:0;padding-left:8px}[dir=rtl] .slimstat-postbox-chart--item,.rtl .slimstat-postbox-chart--item{text-align:right;gap:8px}[dir=rtl] #chartjs-tooltip tr.slimstat-postbox-chart--item{flex-direction:unset !important}[dir=rtl] .slimstat-toggle-prev-datasets{left:120px !important;right:unset;direction:ltr}[dir=rtl] .slimstat-postbox-chart--items{padding:0px 20px 0px 20px}[dir=rtl] .slimstat-postbox-chart--item>span,.rtl .slimstat-postbox-chart--item>span{text-align:right}[dir=rtl] .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color,.rtl .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color{margin-left:0px !important;margin-right:10px !important}[dir=rtl] #chartjs-tooltip .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color,.rtl #chartjs-tooltip .slimstat-postbox-chart--item .slimstat-postbox-chart--item--color{margin-left:10px !important;margin-right:10px !important}[dir=rtl] .slimstat-postbox-chart--item:not(:first-child){margin-bottom:0px}[dir=rtl] .slimstat-postbox-chart--item:not(:last-child){margin-bottom:0px}[dir=rtl] .slimstat-postbox-chart--item .slimstat-postbox-chart--item--prev,.rtl .slimstat-postbox-chart--item .slimstat-postbox-chart--item--prev{margin:0 3px 0 0}[dir=rtl] .tooltip-item-title,.rtl .tooltip-item-title{text-align:right}[dir=rtl] .tooltip-item-content,.rtl .tooltip-item-content{direction:rtl;text-align:right}:root{--box-bar-color: #eff6ff}body.toplevel_page_slimview1,body.slimstat_page_slimview2,body.slimstat_page_slimview3,body.slimstat_page_slimview4,body.slimstat_page_slimview5,body.slimstat_page_slimconfig,body.slimstat_page_slimpro,body.slimstat_page_slimlayout,body.slimstat_page_migration{background:#f5f6fa !important}#slimstat-load-saved-filters{margin:0 5px !important}body.index-php .slimstat-tooltip-bar-wrap{margin:0 5px !important}body.index-php .slimstat-tooltip-trigger:has(.slimstat-tooltip-bar-wrap){border:none !important}body.index-php span.slimstat-tooltip-bar{display:block;height:100%;position:absolute !important;top:0;left:0;background-color:var(--box-bar-color);border-radius:6px}body.index-php .postbox p.slimstat-tooltip-trigger{margin-bottom:4px;margin-top:4px}body.index-php .slimstat-browser-icon,body.index-php .slimstat-flag-icon,body.index-php span.slimstat-flag-container{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}body.index-php .slimstat-author-link img{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}body.index-php .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap{z-index:0 !important;position:absolute;display:block;width:calc(100% - 30px);height:100%;top:0;left:0;margin:0px 15px;box-sizing:border-box}body.index-php .slimstat-tooltip-trigger *{z-index:2 !important;position:relative}body.index-php .slimstat-tooltip-trigger a{max-width:80%;display:inline-block;color:#202224;font-weight:600;vertical-align:middle;font-size:12px}body.index-php .inside:has(.map-container){height:auto !important}body.index-php .map-container{display:flex;flex-wrap:wrap;padding:0px 20px;height:720px !important}body.index-php #map_slim_p6_01{width:100%;height:395px;min-height:395px;flex:1}@media(max-width: 768px){body.index-php #map_slim_p6_01{height:310px;min-height:310px;max-height:310px}}body.index-php .top-countries-wrap{min-width:370px;max-width:370px;margin-left:0px;display:flex;justify-content:center}[dir=rtl] body.index-php .top-countries-wrap{margin-left:0;margin-right:0px}@media(max-width: 768px){body.index-php .top-countries-wrap{margin-left:0;min-width:100%;max-width:100%}[dir=rtl] body.index-php .top-countries-wrap{margin-right:0}}body.index-php .top-countries{flex:1}body.index-php .top-countries h4{font-weight:700;font-size:14px;line-height:100%;letter-spacing:-0.43px}body.index-php .country-bar{display:flex;align-items:center;margin-bottom:20px;position:relative;height:36px}body.index-php .country-bar .country-flag-container{width:24px;height:24px;border-radius:100px;overflow:hidden;margin:0px 10px 0 0px;display:inline-block;min-width:24px}body.index-php .country-bar strong{font-weight:700;font-size:14px;line-height:100%;letter-spacing:0px;vertical-align:middle;position:absolute;top:3px;left:43px;color:#202224}body.index-php .country-bar span{color:#202224;font-weight:800;font-size:12px;line-height:100%;letter-spacing:0px;vertical-align:middle;margin-left:5px;margin-top:23px}body.index-php .country-flag{height:auto;transform:translateX(-13%)}body.index-php .bar-container{background-color:#e5e6e9;height:8px;width:100%;margin-left:10px;border-radius:4px;margin-top:25px;overflow:hidden}body.index-php .bar-fill{height:100%;background-color:#e7294b}body.index-php .map-container{flex-direction:column}body.index-php .top-countries{margin-left:0;margin-top:20px}[dir=rtl] body.index-php .top-countries{margin-left:0;margin-right:0}[id^=slim_] .inside:has(.slimstat-postbox-chart--canvas){margin-bottom:20px !important;height:265px !important}[id^=slim_] .inside:has(.slimstat-postbox-chart--canvas) .slimstat-postbox-chart--canvas{height:240px !important;width:100% !important;min-height:240px !important;max-height:240px !important}#slim_p8_01 .inside,#slim_p8_02 .inside,.report-pages-by-user .inside{margin-bottom:10px !important}#slim_p8_01 .pagination,#slim_p8_02 .pagination,.report-pages-by-user .pagination{position:relative !important}.wrap.slimstat{overflow-x:hidden !important}.wrap.slimstat .postbox:has(p.loading) a.refresh svg{animation:spin 1s linear infinite}.wrap.slimstat .map-container{display:flex;flex-wrap:wrap;padding:0px 20px}@media(min-width: 782px){.wrap.slimstat .map-container{overflow:hidden;height:370px}}.wrap.slimstat #map_slim_p6_01{width:100%;height:395px;min-height:395px;flex:1}@media(max-width: 768px){.wrap.slimstat #map_slim_p6_01{height:310px;min-height:310px;max-height:310px}}.wrap.slimstat .top-countries-wrap{min-width:430px;max-width:430px;margin-left:30px;display:flex;justify-content:center}[dir=rtl] .wrap.slimstat .top-countries-wrap{margin-left:0;margin-right:30px}@media(max-width: 768px){.wrap.slimstat .top-countries-wrap{margin-left:0;min-width:100%;max-width:100%}[dir=rtl] .wrap.slimstat .top-countries-wrap{margin-right:0}}.wrap.slimstat .top-countries{flex:1}.wrap.slimstat .top-countries h4{font-weight:700;font-size:14px;line-height:100%;letter-spacing:-0.43px}.wrap.slimstat .country-bar{display:flex;align-items:center;margin-bottom:20px;position:relative;height:36px}.wrap.slimstat .country-bar .country-flag-container{width:24px;height:24px;border-radius:100px;overflow:hidden;margin:0px 10px 0 0px;display:inline-block;min-width:24px}.wrap.slimstat .country-bar strong{font-weight:700;font-size:14px;line-height:100%;letter-spacing:0px;vertical-align:middle;position:absolute;top:3px;left:43px;color:#202224}.wrap.slimstat .country-bar span{color:#202224;font-weight:800;font-size:12px;line-height:100%;letter-spacing:0px;vertical-align:middle;margin-left:5px;margin-top:23px}.wrap.slimstat .country-flag{height:auto;transform:translateX(-13%)}.wrap.slimstat .bar-container{background-color:#e5e6e9;height:8px;width:100%;margin-left:10px;border-radius:4px;margin-top:25px;overflow:hidden}.wrap.slimstat .bar-fill{height:100%;background-color:#e7294b}@media(max-width: 768px){.wrap.slimstat .map-container{flex-direction:column}.wrap.slimstat .top-countries{margin-left:0;margin-top:20px}[dir=rtl] .wrap.slimstat .top-countries{margin-left:0;margin-right:0}}body.index-php .jqvmap-label,body.toplevel_page_slimview1 .jqvmap-label,body.slimstat_page_slimview2 .jqvmap-label,body.slimstat_page_slimview3 .jqvmap-label,body.slimstat_page_slimview4 .jqvmap-label,body.slimstat_page_slimview5 .jqvmap-label,body.slimstat_page_slimconfig .jqvmap-label,body.slimstat_page_slimpro .jqvmap-label,body.slimstat_page_slimlayout .jqvmap-label,body.slimstat_page_migration .jqvmap-label{z-index:999999;position:absolute !important;background-color:#2b2b2b !important;color:#fff !important;padding:10px !important;transform:translateY(-15px) translateX(50%) !important;max-width:280px !important;min-width:50px !important;font-size:1em !important;line-height:1.5em !important;direction:ltr !important;border-radius:8px !important;backdrop-filter:blur(34px) !important;box-shadow:0px 2px 4px 0px rgba(0,0,0,.0392156863) !important}body.index-php .jqvmap-label h3,body.toplevel_page_slimview1 .jqvmap-label h3,body.slimstat_page_slimview2 .jqvmap-label h3,body.slimstat_page_slimview3 .jqvmap-label h3,body.slimstat_page_slimview4 .jqvmap-label h3,body.slimstat_page_slimview5 .jqvmap-label h3,body.slimstat_page_slimconfig .jqvmap-label h3,body.slimstat_page_slimpro .jqvmap-label h3,body.slimstat_page_slimlayout .jqvmap-label h3,body.slimstat_page_migration .jqvmap-label h3{margin:0 !important;font-size:16px !important;line-height:100% !important;font-weight:bold !important;background-color:#2b2b2b !important;color:#fff !important;letter-spacing:1px !important}body.index-php .jqvmap-label p,body.toplevel_page_slimview1 .jqvmap-label p,body.slimstat_page_slimview2 .jqvmap-label p,body.slimstat_page_slimview3 .jqvmap-label p,body.slimstat_page_slimview4 .jqvmap-label p,body.slimstat_page_slimview5 .jqvmap-label p,body.slimstat_page_slimconfig .jqvmap-label p,body.slimstat_page_slimpro .jqvmap-label p,body.slimstat_page_slimlayout .jqvmap-label p,body.slimstat_page_migration .jqvmap-label p{font-family:Inter !important;font-weight:600 !important;font-size:12px !important;line-height:100% !important;letter-spacing:1px !important;background-color:#2b2b2b !important;color:#fff !important}body.index-php .jqvmap-label canvas,body.toplevel_page_slimview1 .jqvmap-label canvas,body.slimstat_page_slimview2 .jqvmap-label canvas,body.slimstat_page_slimview3 .jqvmap-label canvas,body.slimstat_page_slimview4 .jqvmap-label canvas,body.slimstat_page_slimview5 .jqvmap-label canvas,body.slimstat_page_slimconfig .jqvmap-label canvas,body.slimstat_page_slimpro .jqvmap-label canvas,body.slimstat_page_slimlayout .jqvmap-label canvas,body.slimstat_page_migration .jqvmap-label canvas{position:absolute;color:#2b2b2b !important;background:#2b2b2b !important;border:0 dashed rgba(0,0,0,0);background-color:#2b2b2b !important;width:12px !important;height:12px !important;transform:rotate(135deg) translateX(0px) translateY(1px);line-height:unset !important;border-top-right-radius:4px;display:inline-block;bottom:-5px;left:calc(50% - 6px)}.export-pro-badge{margin-left:3px;margin-bottom:-5px}[dir=rtl] .export-pro-badge{margin-left:0;margin-right:3px}.wrap.slimstat a,[id^=slim_] a{outline:none;text-decoration:none}[id^=slim_] a.slimstat-delete-entry:before{color:#ff3636}.slimstat-float-right{float:right}#slimstat-filters-post{display:none}#slimstat-filters-form{margin-top:10px;padding:5px;position:relative;border-radius:5px}#slimstat-filters input.text,#slimstat-filters select{vertical-align:initial;width:15%}#screen-meta-links{position:absolute;right:0;z-index:1}[dir=rtl] #screen-meta-links{left:0;right:auto}#slimstat-date-filters{padding:4px 10px 6px 20px;position:absolute;right:5px;top:5px;background-color:#e8294c;border-radius:50px}#slimstat-date-filters>a{color:#fff;white-space:nowrap}#slimstat-date-filters>a::after{content:"";font:normal 20px/1 "dashicons";vertical-align:bottom}#slimstat-date-filters>a.open::after{content:""}#slimstat-date-filters .dropdown{background-color:#fff;box-shadow:5px 0px 30px rgba(0,0,0,.1);color:#222;display:none;padding:12px;position:absolute;right:-4px;top:35px;width:410px;z-index:120;border-radius:8px}[dir=rtl] #slimstat-date-filters .dropdown{right:auto;left:-4px}#slimstat-date-filters .dropdown strong{clear:both;display:block;font-weight:normal;padding:10px 5px 5px 0;text-transform:uppercase}#slimstat-date-filters .dropdown select,#slimstat-date-filters .dropdown input{margin:0 5px 5px 0;height:27px;width:130px}#slimstat-date-filters .dropdown select.short,#slimstat-date-filters .dropdown input.short{width:65px}#slimstat-date-filters .dropdown .ui-datepicker-trigger{float:right;margin:2px 5px 0 0}[dir=rtl] #slimstat-date-filters .dropdown .ui-datepicker-trigger{margin:2px 0 0 5px}#slimstat-current-filters{margin-bottom:5px;background-color:#fff;color:#616060;font-size:14px;overflow:hidden;padding:10px 10px;position:relative;border-radius:5px;margin-top:0px;border:solid 1px #f0f0f0}@media(max-width: 400px){#slimstat-current-filters .button-secondary{margin:2px auto}}#slimstat-current-filters .slimstat-remove-filter{color:#fff}#slimstat-current-filters .slimstat-filter-list{float:left;margin:0;padding:4px 0 5px 5px}#slimstat-current-filters .slimstat-filter-list li{display:inline;margin:0 20px 0 0;vertical-align:middle}.slimstat-filter-action-button{float:right;margin-left:10px !important}.wrap.slimstat .meta-box-sortables{display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;margin-right:-1.3%}@media(max-width: 1560px){.wrap.slimstat .meta-box-sortables{margin-right:-0.5%}}[dir=rtl] .wrap.slimstat .meta-box-sortables{margin-right:0;margin-left:-1.3%}@media(max-width: 1560px){[dir=rtl] .wrap.slimstat .meta-box-sortables{margin-left:-0.5%}}.wrap.slimstat .postbox,.wrap.slimstat .sortable-placeholder{box-sizing:border-box;float:left;margin-bottom:15px;min-width:24.3%;overflow:hidden;width:24.3%}.wrap.slimstat .postbox.large{width:49.1%}.wrap.slimstat .postbox.extralarge{width:73.9%}.wrap.slimstat .postbox.full-width{width:98.7%}.sortable-placeholder{background-color:#ccc;border:1px dashed #bbb;margin-bottom:9px}.wrap.slimstat .postbox h3{border-bottom:1px solid #ddd;font-size:1.2em;margin:0;padding:10px}.wrap.slimstat .postbox.tall h3{cursor:auto}.slimstat-tooltip-content{display:none}.slimstat .no-refresh .refresh{display:none}[id^=slim_] p.pagination{font-weight:600;color:#000;font-size:14px}[id^=slim_] p.pagination a{color:#151515;float:right;margin-left:5px}[id^=slim_] .inside{height:240px;margin:0 !important;overflow:auto;padding:0 !important;margin-bottom:45px !important}.wrap.slimstat .postbox.tall .inside{height:465px}.map-wrap .inside{height:370px !important}@media(max-width: 767px){.map-wrap .inside{height:auto !important}}[id^=slim_] p{border-bottom:1px solid #eee;line-height:1.5em;margin:0;min-height:20px;padding:10px 10px;position:relative;word-wrap:break-word}[id^=slim_] p:last-child{border-bottom:0}[id^=slim_] p .slimstat-tooltip-trigger.corner{-moz-transform:scaleX(-1);-o-transform:scaleX(-1);-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH";left:0}[id^=slim_] p span{float:right}[id^=slim_] p span.pageview-screenres{margin-left:10px}[id^=slim_] .inline-icon{background-color:rgba(0,0,0,0);background-position:0 0;background-repeat:no-repeat;display:inline-block;height:18px;line-height:18px;margin-right:5px;vertical-align:middle;width:16px;filter:grayscale(40%);transition:.3s all ease-out}[id^=slim_] .inline-icon:hover{filter:grayscale(0)}[id^=slim_] .spaced{margin-left:15px}#dashboard-widgets-wrap .whois{display:none}[id^=slim_] .debug{background-color:#000;color:#fff;display:block;font-family:monospace;opacity:.8;padding:20px;position:relative}.little-color-box{background-color:#eee;border:1px solid #aaa;display:block;float:left;height:15px;margin-right:10px;width:15px}[id^=slim_] .header{background-color:#f8f8f8;color:#111}[id^=slim_] .header.is-search-engine,.little-color-box.is-search-engine{background-color:#e2dbff;color:#151515}[id^=slim_] .header.is-direct,.little-color-box.is-direct{background-color:#f8f8f8;color:#111}[id^=slim_] .header.is-known-user,.little-color-box.is-known-user{background-color:#ddf0ff}[id^=slim_] .header.is-known-visitor,.little-color-box.is-known-visitor{background-color:#ffe9c8}[id^=slim_] .header.is-spam,.little-color-box.is-spam{background-color:#eeff89;color:#222}[id^=slim_] p.loading{text-align:center;width:100%;padding:0;margin:0;top:50%;line-height:0;transform:translateY(-50%);overflow:hidden}[id^=slim_] p.nodata{border:0;color:#999;text-align:center;position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);width:90%}.chart-placeholder{height:280px;overflow:hidden}body.toplevel_page_slimview1 div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.toplevel_page_slimview1 div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.toplevel_page_slimview1 .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.toplevel_page_slimview1 .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.toplevel_page_slimview1 .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview2 div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview2 div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview2 .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview2 .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview2 .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview3 div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview3 div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview3 .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview3 .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview3 .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview4 div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview4 div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview4 .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview4 .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview4 .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview5 div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview5 div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview5 .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview5 .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimview5 .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimconfig div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimconfig div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimconfig .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimconfig .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimconfig .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_migration div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_migration div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_migration .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_migration .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_migration .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimpro div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimpro div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimpro .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimpro .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimpro .error:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimlayout div[class*=-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimlayout div[class*=admin-notice]:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimlayout .notice:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimlayout .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice),body.slimstat_page_slimlayout .error:not(.slimstat-notice):not(.slimstat-migration-notice){display:none !important}.ui-widget-overlay{background-color:#222;filter:alpha(opacity=60);height:100%;opacity:.6;position:fixed;top:0;width:100%;z-index:100 !important}.ui-dialog.slimstat .ui-dialog-titlebar{background:#4b8df8;border:0;border-radius:0;color:#fff;font-family:"Open Sans",sans-serif;font-size:1.3em;font-weight:normal;height:22px;line-height:1.3em;margin:0;padding:5px 10px}.tooltip-item-title{font-size:13px;font-weight:500}.tooltip-item-content{font-size:13px;font-weight:normal;font-style:italic}.ui-dialog.slimstat .ui-dialog-titlebar-close{background-color:rgba(0,0,0,0);border:0;color:#fff;float:right;line-height:1.3em;padding:0}.ui-dialog.slimstat .ui-dialog-titlebar-close:before{content:"";font-family:slimstat}.ui-dialog .ui-dialog-content{clear:both}#slimstat-modal-dialog{background-color:#fff;display:none;max-height:650px !important;overflow:auto;padding:0;width:auto}#slimstat-modal-dialog p{margin:0;padding:10px}#dashboard-widgets [id*=slim_p].postbox .inside{height:281px}#dashboard-widgets [id*=slim_p][id*=_01].postbox .inside{height:290px}#dashboard-widgets #slim_p7_02.postbox .inside{height:320px}.closed .slimScrollDiv{height:0 !important}.nav-tabs{margin:20px 1px 0}.nav-tab{font-size:14px;margin:0 5px 0 0}.nav-tab a{color:#151515;display:block;text-decoration:none}.form-table{border:1px solid #ccc;margin-top:0;overflow:hidden}.form-table *:not(.bootstrap-switch-container):not(input):not(button):not(label):not(select):not(textarea):not(a):not(code):not(pre):not(.description){max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-wrap:auto}.form-table th{font-weight:400;padding:15px 10px}.slimstat-options-section-header{background-color:#fff !important;font-size:1.4em;font-weight:bold !important;margin:0;padding:5px 10px}.form-table h3{margin-top:0}.form-table td span.block-element{padding:0 30px 0 0}.form-table .description{color:#999;display:block;font-style:normal;margin-top:5px}.form-table #slimstat-filter-name,.form-table #slimstat-filter-operator,.form-table #slimstat-filter-value{width:20%}[id*=form-slimstat-options] input[type=number].small-text{width:85px}[class*=bootstrap-switch-id-addon_network_settings]{float:right}.CodeMirror{height:auto !important}.wp-list-table.slimstat-addons{margin-bottom:20px}.wp-list-table.slimstat-addons tbody th{border-left:5px solid #ccc}[dir=rtl] .wp-list-table.slimstat-addons tbody th{border-left:0;border-right:5px solid #ccc}.wp-list-table.slimstat-addons th,.wp-list-table.slimstat-addons td{border-bottom:1px solid #ccc}.wp-list-table.slimstat-addons .active th{border-color:#10a062;border-style:solid;border-radius:0 0 0 5px}[dir=rtl] .wp-list-table.slimstat-addons .active th{border-radius:0 0 5px 0}.wp-list-table.slimstat-addons .active td{border:0}.column-wp-slimstat{text-align:center !important;width:3em}.slimstat-icon:before{content:"";font-family:dashicons}.rtl #slimstat-current-filters .slimstat-filter-list{padding:0 0 0 75px}.rtl #slimstat-current-filters .slimstat-filter-list li{margin-left:20px;margin-right:0}.rtl #slimstat-remove-all-filters{left:5px;right:inherit}.rtl #slimstat-date-filters{left:5px;right:inherit}.rtl #slimstat-date-filters span{left:0;right:inherit}.rtl .wrap.slimstat .postbox{float:right}.rtl .slimScrollBar{left:2px !important;right:inherit !important}.rtl [id^=slim_] p.pagination a{float:left}.rtl [id^=slim_] p span{float:left}.rtl .qtip-content{text-align:right}.rtl .form-table td span.block-element{padding:0 0 0 30px}.rtl .slimstat-browser-icon,.rtl .slimstat-flag-icon,.rtl span.slimstat-flag-container{float:right !important;margin:0px 0px 0px 10px !important}.rtl .slimstat-author-link img{float:right !important;margin:0px 0px 0px 10px !important}.rtl .slimstat-float-right{float:left}.rtl #slimstat-date-filters .dropdown .ui-datepicker-trigger{float:left;margin:2px 0 0 5px}.rtl #slimstat-current-filters .slimstat-filter-list{float:right;padding:4px 5px 5px 0}.rtl .slimstat-filter-action-button{float:left;margin-right:10px !important;margin-left:0 !important}.rtl .wrap.slimstat .sortable-placeholder{float:right}.rtl [id^=slim_] p.pagination a{float:left;margin-right:5px;margin-left:0}.rtl [id^=slim_] p span{float:left}.rtl [id^=slim_] p span.pageview-screenres{margin-right:10px;margin-left:0}.rtl [id^=slim_] .inline-icon{margin-left:5px;margin-right:0}.rtl [id^=slim_] .spaced{margin-right:15px;margin-left:0}.rtl .little-color-box{float:right;margin-left:10px;margin-right:0}.rtl .ui-dialog.slimstat .ui-dialog-titlebar-close{float:left}.rtl [class*=bootstrap-switch-id-addon_network_settings]{float:left}.rtl .slimstat-layout .postbox,.rtl .slimstat-layout .sortable-placeholder{float:right;margin:0 0 10px 10px}.rtl .ui-datepicker .ui-datepicker-buttonpane button{float:left}.rtl .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.rtl .ui-datepicker-multi .ui-datepicker-group{float:right}.rtl .tag-editor li{float:right}.rtl .tag-editor div{float:right}.rtl .tag-editor .tag-editor-tag{padding-right:5px;padding-left:0;border-radius:2px 0 0 2px}.rtl .tag-editor .tag-editor-delete{padding-right:4px;padding-left:3px;border-radius:0 2px 2px 0}.rtl .ui-autocomplete{right:0;left:auto}@media(min-width: 780px){.rtl .backdrop-container{margin-right:-20px;margin-left:0;padding-right:20px;padding-left:0}}.rtl .slimstat-filter-link .avatar{margin-left:2px;margin-right:0}.rtl #slimstat-date-filters a.slimstat-filter-link{float:right;margin:0 0 5px 5px}.rtl .slimstat-header-buttons{float:left}.rtl .slimstat-header-buttons a.slimstat-font-download span.dashicons::before{margin-left:2px;margin-right:0}.rtl .slimstat-browser-icon,.rtl .slimstat-flag-icon,.rtl span.slimstat-flag-container{float:right !important;margin:0px 0px 0px 10px !important}.rtl .slimstat-author-link img{float:right !important;margin:0px 0px 0px 10px !important}.rtl .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap{right:0;left:auto;margin:0px 15px 0px 0px}.rtl .pagination{right:0;left:auto}.rtl .slimstat-header{margin-right:-20px;margin-left:0}.rtl .slimstat-header .go-pro{text-align:right}.rtl .slimstat-header .go-pro p{text-align:right}.rtl .slimstat-header .pro-badge{right:auto;left:20px}.rtl .slimstat-header .pro-badge .icon{margin-left:7px;margin-right:0}.rtl .slimstat-header .go-pro a .icon{margin-left:0;margin-right:5px;transform:scaleX(-1)}.rtl .slimstat-pro-modal #slimstat-pro-modal-close{left:10%;right:auto}.rtl .slimstat-pro-modal .features-flex-box .feature-item h6 .icon{margin-left:7px;margin-right:0}.rtl .slimstat-pro-modal .features-flex-box .feature-item .more-info-icon{margin-right:3px;margin-left:0}@media screen and (max-width: 800px){.rtl #slimstat-date-filters{left:inherit;right:inherit}}@media screen and (max-width: 600px){.rtl [id^=slim_] .spaced{margin-right:5px;margin-left:5px}.rtl [id^=slim_] .inline-icon{margin-left:2px;margin-right:0}.rtl [id^=slim_] p span.pageview-screenres{margin-right:3px;margin-left:0}}@media screen and (max-width: 400px){.rtl [id^=slim_] .spaced{margin-right:8px;margin-left:8px}.rtl [id^=slim_] .inline-icon{margin-left:3px;margin-right:0}.rtl [id^=slim_] p span.pageview-screenres{margin-right:5px;margin-left:0}}.slimstat-layout .postbox-container{float:none;margin-top:20px;overflow:hidden;border:solid 1px #f0f0f0;border-radius:10px}.slimstat-layout .meta-box-sortables{overflow:hidden;padding:10px}.slimstat-layout .postbox-container span.title{background-color:silver;color:#fff;font-size:16px;margin-bottom:0;padding:10px}.slimstat-layout .postbox,.slimstat-layout .sortable-placeholder{float:left;margin:0 10px 10px 0;min-width:285px}.slimstat-layout .sortable-placeholder{border:2px dashed #72aee6;background-color:#f0f6fc;visibility:visible !important;height:80px;border-radius:5px}.slimstat-layout .postbox.ui-sortable-helper{opacity:.65;box-shadow:0 5px 15px rgba(0,0,0,.3)}.slimstat-layout h3{border:0;font-weight:300;font-size:1.1em;margin:0;padding:5px 10px}@media screen and (max-width: 1560px){.wrap.slimstat .postbox{width:32.8%}.wrap.slimstat .postbox.large{width:66.1%}.wrap.slimstat .postbox.extralarge,.wrap.slimstat .postbox.full-width{width:99.4%}.wrap.slimstat .postbox h3{font-size:1em;line-height:1.2em}.wrap.slimstat .postbox p{font-size:1em;line-height:1.5em}.wrap.slimstat .postbox p span.details{float:none !important;display:block;padding:0px !important;margin-top:10px;align-items:center}.wrap.slimstat .postbox p span.details>*:nth-last-child(1){position:relative;float:right;right:-25px}.wrap.slimstat .postbox p span.details>*:nth-last-child(2){position:relative;float:right;right:25px}[id^=slim_] .spaced{margin:0}}.index-php div#slim_p7_02.postbox .pageview-screenres{float:none}.index-php div#slim_p7_02.postbox .details{position:relative;float:none;display:block}.index-php div#slim_p7_02.postbox .details .slimstat-font-edit{float:right}.index-php div#slim_p7_02.postbox .details .spaced{margin-left:0px}@media screen and (max-width: 1080px){.wrap.slimstat .postbox{width:49.125%}.wrap.slimstat .postbox.large,.wrap.slimstat .postbox.extralarge,.wrap.slimstat .postbox.full-width{width:99%}}@media screen and (max-width: 800px){#slimstat-filters input.text,#slimstat-filters select{margin:0 .5% 0 0;width:26%}.wp-core-ui .button-secondary{height:35px}#slimstat-date-filters{margin-top:5px;position:relative;right:inherit;top:inherit}.wrap.slimstat .postbox,.wrap.slimstat .postbox.large,.wrap.slimstat .postbox.extralarge,.wrap.slimstat .postbox.full-width{margin:0 0 10px;width:99.5%}.wrap.slimstat .postbox h3{font-size:1.4em;line-height:1em}.wrap.slimstat .postbox p{line-height:1.4em}.nav-tab{display:block;margin:0}.form-table th{font-size:1.4em}.form-table th label{font-size:1em}.form-table td{padding:10px}.form-table .button-primary,.form-table .button-secondary{height:30px;margin-bottom:5px;text-align:center;width:100%}.form-table #slimstat-filter-name,.form-table #slimstat-filter-operator,.form-table #slimstat-filter-value{width:100%}[id^=slim_] .users .column-name{display:none}[id^=slim_] p{padding:8px 10px;word-break:break-all;overflow-wrap:break-word;hyphens:auto}[id^=slim_] p .slimstat-filter-link{max-width:100%;display:inline-block;word-break:break-all;overflow-wrap:break-word}[id^=slim_] p .details{display:block;margin-top:5px;font-size:.9em;line-height:1.3em}[id^=slim_] p .spaced{margin-left:8px;margin-right:8px}[id^=slim_] .inline-icon{margin-right:3px}[id^=slim_] p span.pageview-screenres{margin-left:5px;font-size:.8em}[id^=slim_] p .slimstat-filter-link{position:relative}[id^=slim_] p .slimstat-filter-link[title]{cursor:help}[id^=slim_] p{min-width:0;flex-shrink:1}[id^=slim_] p .slimstat-filter-link{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;vertical-align:top}[id^=slim_] p .slimstat-filter-link:hover{white-space:normal;word-break:break-all;overflow:visible;position:relative;z-index:10;background-color:#fff;box-shadow:0 2px 8px rgba(0,0,0,.15);padding:4px;border-radius:3px}[id^=slim_] p.access-log-entry{display:flex;flex-direction:column;align-items:flex-start;gap:4px}[id^=slim_] p.access-log-entry .details{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:.85em;color:#666;margin-top:4px}[id^=slim_] p.access-log-entry{flex-direction:column;align-items:stretch}[id^=slim_] p.access-log-entry .details{flex-direction:column;align-items:flex-start;gap:4px}[id^=slim_] p.access-log-entry .details>*{margin-bottom:2px}}@media screen and (max-width: 600px){[id^=slim_] p{padding:6px 8px;font-size:.9em}[id^=slim_] p .details{font-size:.8em;margin-top:3px}[id^=slim_] .spaced{margin-left:5px;margin-right:5px}[id^=slim_] .inline-icon{margin-right:2px;width:14px;height:16px}[id^=slim_] p span.pageview-screenres{font-size:.7em;margin-left:3px}[id^=slim_] p.header{display:flex;flex-direction:column;align-items:flex-start}[id^=slim_] p.header .inline-icon{margin-bottom:2px}[id^=slim_] p.access-log-entry .details{font-size:.8em}}@font-face{font-family:"slimstat";src:url("slimstat.eot?58272494");src:url("slimstat.eot?58272494#iefix") format("embedded-opentype"),url("slimstat.svg?58272494#fontello") format("svg");font-weight:normal;font-style:normal}@font-face{font-family:"slimstat";src:url("data:application/octet-stream;base64,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") format("woff"),url("data:application/octet-stream;base64,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") format("truetype")}[class^=slimstat-font-]:before,[class*=" slimstat-font-"]:before{color:#616060;font-family:"slimstat";font-style:normal;font-weight:normal;font-size:14px;text-decoration:inherit;width:1em;margin:0 .2em;text-align:center;font-variant:normal;text-transform:none;line-height:1em}.slimstat-font-trash:before{content:""}.slimstat-font-clock:before{content:""}.slimstat-font-search:before{content:""}.slimstat-font-cancel:before{content:"";background:url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 10 10' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_417_931)'%3E%3Cpath d='M5.89353 4.99459L8.95228 1.93609C9.06613 1.81821 9.12912 1.66034 9.1277 1.49646C9.12628 1.33259 9.06055 1.17583 8.94466 1.05995C8.82878 0.944073 8.67203 0.878342 8.50815 0.876918C8.34428 0.875494 8.18641 0.938491 8.06853 1.05234L5.00978 4.11084L1.95128 1.05234C1.89328 0.994263 1.8244 0.94819 1.74858 0.916756C1.67276 0.885321 1.59148 0.869141 1.5094 0.869141C1.42733 0.869141 1.34605 0.885321 1.27023 0.916756C1.19441 0.94819 1.12553 0.994263 1.06753 1.05234C1.00945 1.11034 0.96338 1.17922 0.931945 1.25504C0.90051 1.33086 0.88433 1.41214 0.88433 1.49421C0.88433 1.57629 0.90051 1.65757 0.931945 1.73339C0.96338 1.80921 1.00945 1.87809 1.06753 1.93609L4.12603 4.99459L1.06053 8.06034C0.972939 8.14767 0.913252 8.25904 0.889038 8.38033C0.864824 8.50162 0.877172 8.62737 0.924517 8.74164C0.971862 8.85591 1.05207 8.95354 1.15498 9.02216C1.25788 9.09078 1.37884 9.1273 1.50253 9.12709C1.66253 9.12709 1.82253 9.06609 1.94453 8.94409L5.01003 5.87834L8.06853 8.93684C8.12652 8.99495 8.19542 9.04103 8.27127 9.07244C8.34712 9.10384 8.42843 9.11995 8.51053 9.11984C8.63411 9.11981 8.75491 9.08315 8.85765 9.01449C8.9604 8.94583 9.04049 8.84825 9.08779 8.73408C9.1351 8.61991 9.14749 8.49429 9.12342 8.37308C9.09934 8.25186 9.03987 8.14051 8.95253 8.05309L5.89353 4.99459Z' fill='%23FF3636'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_417_931'%3E%3Crect width='9' height='9' fill='white' transform='translate(0.509766 0.494141)'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A") no-repeat center center/contain;width:12px;height:12px;display:inline-block;transform:translateY(1px)}.slimstat-font-upload:before{content:""}.slimstat-font-download-cloud:before{content:""}.slimstat-font-upload-cloud:before{content:""}.slimstat-font-location-1:before{content:""}.slimstat-font-arrows-cw-1:before{content:""}.slimstat-font-login:before{content:""}.slimstat-font-logout:before{content:""}.slimstat-font-angle-left:before{content:""}.slimstat-font-angle-right:before{content:""}.slimstat-font-angle-double-left:before{content:""}.slimstat-font-angle-double-right:before{content:""}.slimstat-font-arrows-cw:before{content:""}.slimstat-font-doc:before{content:""}.slimstat-font-spin4:before{content:"";font-size:26px;overflow:hidden;margin:0}.slimstat-font-edit:before{content:""}.slimstat-font-gauge:before{content:""}.slimstat-font-stopwatch:before{content:""}.slimstat-font-docs:before{content:""}.slimstat-font-user-plus:before{content:""}.slimstat-font-user-times:before{content:""}.slimstat-font-minus-circled:before{content:""}.animate-spin{-moz-animation:spin 3s infinite linear;-o-animation:spin 3s infinite linear;-webkit-animation:spin 3s infinite linear;animation:spin 3s infinite linear;display:inline-block;line-height:1em}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-o-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@-ms-keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spin{0%{-moz-transform:rotate(0deg);-o-transform:rotate(0deg);-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(359deg);-o-transform:rotate(359deg);-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.qtip{position:absolute;left:-28000px;top:-28000px;display:none;max-width:280px;min-width:50px;font-size:1em;line-height:1.5em;direction:ltr;box-shadow:none;border-radius:5px;padding:0}.qtip-content,.qtip-titlebar{position:relative;overflow:hidden}.qtip-content{padding:5px 9px;text-align:left;word-wrap:break-word}[dir=rtl] .qtip-content{text-align:right}.qtip-titlebar{padding:5px 35px 5px 10px;border-radius:0 0 1px;font-weight:700}[dir=rtl] .qtip-titlebar{padding:5px 10px 5px 35px}.qtip-titlebar+.qtip-content{border-top-width:0 !important}.qtip-close{position:absolute;right:-9px;top:-9px;z-index:11;cursor:pointer;outline:0;border:1px solid rgba(0,0,0,0)}[dir=rtl] .qtip-close{right:auto;left:-9px}.qtip-titlebar .qtip-close{right:4px;top:50%;margin-top:-9px}[dir=rtl] .qtip-titlebar .qtip-close{right:auto;left:4px}* html .qtip-titlebar .qtip-close{top:16px}.qtip-icon .ui-icon,.qtip-titlebar .ui-icon{display:block;text-indent:-1000em;direction:ltr}.qtip-icon,.qtip-icon .ui-icon{-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;text-decoration:none}.qtip-icon .ui-icon{width:18px;height:14px;line-height:1.5em;text-align:center;text-indent:0;font:normal 700 1em Tahoma,sans-serif;color:inherit;background:-100em -100em no-repeat}.qtip-default{background-color:#2b2b2b;font-style:italic;padding-top:12px;padding-right:16px;padding-bottom:12px;padding-left:16px;border-radius:12px;color:#9ba1a6;font-weight:600;font-size:12px;line-height:20px;letter-spacing:0px;font-style:normal;text-decoration:none !important;transform:translateX(9px) translateY(-1px)}[dir=rtl] .qtip-default{transform:translateX(-9px) translateY(-1px)}.qtip-default strong{color:#fff;font-weight:700;font-size:14px;line-height:20px;letter-spacing:0px;font-style:normal;text-decoration:none !important}.qtip-default .qtip-titlebar{background-color:#ffef93}.qtip-default .qtip-icon{border-color:#ccc;background:#f1f1f1;color:#777}.qtip-default .qtip-titlebar .qtip-close{border-color:#aaa;color:#111}.qtip .qtip-tip{margin:0 auto;overflow:hidden;z-index:10}.qtip .qtip-tip,x:-o-prefocus{visibility:hidden}.qtip .qtip-tip,.qtip .qtip-tip .qtip-vml,.qtip .qtip-tip canvas{position:absolute;color:#2b2b2b !important;background:#2b2b2b !important;border:0 dashed rgba(0,0,0,0);background-color:#2b2b2b !important;width:12px !important;height:12px !important;transform:rotate(135deg) translateX(0px) translateY(1px);line-height:unset !important;border-top-right-radius:4px;display:inline-block}[dir=rtl] .qtip .qtip-tip,[dir=rtl] .qtip .qtip-tip .qtip-vml,[dir=rtl] .qtip .qtip-tip canvas{border-top-right-radius:0;border-top-left-radius:4px}.qtip .qtip-tip canvas{top:0;left:0;display:none !important}[dir=rtl] .qtip .qtip-tip canvas{left:auto;right:0}.qtip .qtip-tip .qtip-vml{behavior:url(#default#VML);display:inline-block;visibility:visible}/*! jQuery UI - v1.10.3 | https://jqueryui.com */#ui-datepicker-div{display:none}.ui-datepicker{background-color:#fff;width:17em;padding:8px;border-radius:8px;box-shadow:5px 0px 40px rgba(0,0,0,.2);border:none !important;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:4px;width:26px;height:26px;cursor:pointer}.ui-datepicker .ui-datepicker-prev:hover,.ui-datepicker .ui-datepicker-next:hover{background-color:#fff}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;top:50%;transform:translate(-50%, -50%);font-size:15px;font-weight:600;color:#616060;transition:.3s all ease-out}.ui-datepicker .ui-datepicker-prev span:hover,.ui-datepicker .ui-datepicker-next span:hover{color:#e8294c}.ui-datepicker .ui-datepicker-prev{left:2px}[dir=rtl] .ui-datepicker .ui-datepicker-prev{left:auto;right:2px}.ui-datepicker .ui-datepicker-next{right:2px}[dir=rtl] .ui-datepicker .ui-datepicker-next{right:auto;left:2px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month-year{width:100%}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:49%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:700;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}[dir=rtl] .ui-datepicker td span,[dir=rtl] .ui-datepicker td a{text-align:left}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}[dir=rtl] .ui-datepicker .ui-datepicker-buttonpane{border-left:0;border-right:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em;width:auto;overflow:visible}[dir=rtl] .ui-datepicker .ui-datepicker-buttonpane button{float:left}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}[dir=rtl] .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:right}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}[dir=rtl] .ui-datepicker-multi .ui-datepicker-group{float:right}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}[dir=rtl] .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,[dir=rtl] .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:1px;border-right-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}[dir=rtl] .ui-datepicker-multi .ui-datepicker-buttonpane{clear:right}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}[dir=rtl] .ui-datepicker-rtl .ui-datepicker-buttonpane{clear:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}[dir=rtl] .ui-datepicker-rtl .ui-datepicker-buttonpane button{float:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}[dir=rtl] .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,[dir=rtl] .ui-datepicker-rtl .ui-datepicker-group{float:left}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}[dir=rtl] .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,[dir=rtl] .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:1px;border-left-width:0}/*! bootstrap-switch - v3.3.4 | https://www.bootstrap-switch.org */.bootstrap-switch{display:inline-block;direction:ltr;cursor:pointer;border-radius:4px;border:1px solid #ccc;position:relative;text-align:left;overflow:hidden;line-height:8px;z-index:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;vertical-align:middle;-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}[dir=rtl] .bootstrap-switch{direction:rtl;text-align:right}.bootstrap-switch .bootstrap-switch-container{display:inline-block;top:0;border-radius:4px;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on,.bootstrap-switch .bootstrap-switch-label{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;cursor:pointer;display:inline-block !important;height:100%;padding:6px 12px;font-size:14px;line-height:20px}.bootstrap-switch .bootstrap-switch-handle-off,.bootstrap-switch .bootstrap-switch-handle-on{text-align:center;z-index:1}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-primary,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-primary{color:#fff;background:#337ab7}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-info,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-info{color:#fff;background:#5bc0de}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-success,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-success{color:#fff;background:#e8294c}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-warning,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-warning{background:#f0ad4e;color:#fff}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-danger,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-danger{color:#fff;background:#d9534f}.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default,.bootstrap-switch .bootstrap-switch-handle-on.bootstrap-switch-default{color:#000;background:#eee}.bootstrap-switch .bootstrap-switch-label{text-align:center;margin-top:-1px;margin-bottom:-1px;z-index:100;color:#333;background:#fff}.bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:3px;border-top-left-radius:3px}[dir=rtl] .bootstrap-switch .bootstrap-switch-handle-on{border-bottom-left-radius:0;border-top-left-radius:0;border-bottom-right-radius:3px;border-top-right-radius:3px}.bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:3px;border-top-right-radius:3px}[dir=rtl] .bootstrap-switch .bootstrap-switch-handle-off{border-bottom-right-radius:0;border-top-right-radius:0;border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch input[type=radio],.bootstrap-switch input[type=checkbox]{position:absolute !important;top:0;left:0;margin:0;z-index:-1;opacity:0 !important;filter:alpha(opacity=0)}[dir=rtl] .bootstrap-switch input[type=radio],[dir=rtl] .bootstrap-switch input[type=checkbox]{left:auto;right:0}.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-label{padding:1px 5px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-small .bootstrap-switch-label{padding:5px 10px;font-size:12px;line-height:1.5}.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-large .bootstrap-switch-label{padding:6px 16px;font-size:18px;line-height:1.3333333}.bootstrap-switch.bootstrap-switch-disabled,.bootstrap-switch.bootstrap-switch-indeterminate,.bootstrap-switch.bootstrap-switch-readonly{cursor:default !important}.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-disabled .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-indeterminate .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-off,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-handle-on,.bootstrap-switch.bootstrap-switch-readonly .bootstrap-switch-label{opacity:.5;filter:alpha(opacity=50);cursor:default !important}.bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-left .5s;-o-transition:margin-left .5s;transition:margin-left .5s}[dir=rtl] .bootstrap-switch.bootstrap-switch-animate .bootstrap-switch-container{-webkit-transition:margin-right .5s;-o-transition:margin-right .5s;transition:margin-right .5s}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:0 3px 3px 0}[dir=rtl] .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on{border-radius:3px 0 0 3px}.bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:3px 0 0 3px}[dir=rtl] .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off{border-radius:0 3px 3px 0}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:3px;border-top-right-radius:3px}[dir=rtl] .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label,[dir=rtl] .bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label{border-bottom-right-radius:0;border-top-right-radius:0;border-bottom-left-radius:3px;border-top-left-radius:3px}.bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label,.bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:3px;border-top-left-radius:3px}[dir=rtl] .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label,[dir=rtl] .bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label{border-bottom-left-radius:0;border-top-left-radius:0;border-bottom-right-radius:3px;border-top-right-radius:3px}.tag-editor{list-style-type:none;padding:0 5px 0 0;margin:0;overflow:hidden;border:1px solid #eee;cursor:text;font:normal 14px sans-serif;color:#555;background:#fff;line-height:20px}[dir=rtl] .tag-editor{padding:0 0 0 5px}.tag-editor li{display:block;float:left;overflow:hidden;margin:3px 0}[dir=rtl] .tag-editor li{float:right}.tag-editor div{float:left;padding:0 4px}[dir=rtl] .tag-editor div{float:right}.tag-editor .placeholder{padding:0 8px;color:#bbb}.tag-editor .tag-editor-spacer{padding:0;width:8px;overflow:hidden;color:rgba(0,0,0,0);background:none}[dir=rtl] .tag-editor .tag-editor-spacer{width:8px}.tag-editor input{vertical-align:inherit;border:0;outline:none;padding:0;margin:0;cursor:text;font-family:inherit;font-weight:inherit;font-size:inherit;font-style:inherit;box-shadow:none;background:none;color:#151515}.tag-editor-hidden-src{position:absolute !important;left:0;top:0;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;padding:0;margin:0;border:0}[dir=rtl] .tag-editor-hidden-src{left:auto;right:0}.tag-editor ::-ms-clear{display:none}.tag-editor .tag-editor-tag{padding-left:5px;color:#46799b;background:#e0eaf1;white-space:nowrap;overflow:hidden;cursor:pointer;border-radius:2px 0 0 2px}[dir=rtl] .tag-editor .tag-editor-tag{padding-left:0;padding-right:5px;border-radius:0 2px 2px 0}.tag-editor .tag-editor-delete{background:#e0eaf1;cursor:pointer;border-radius:0 2px 2px 0;padding-left:3px;padding-right:4px}[dir=rtl] .tag-editor .tag-editor-delete{padding-left:4px;padding-right:3px;border-radius:2px 0 0 2px}.tag-editor .tag-editor-delete i{line-height:18px;display:inline-block}.tag-editor .tag-editor-delete i:before{font-size:16px;color:#8ba7ba;content:"×";font-style:normal}.tag-editor .tag-editor-delete:hover i:before{color:#d65454}.tag-editor .tag-editor-tag.active+.tag-editor-delete,.tag-editor .tag-editor-tag.active+.tag-editor-delete i{visibility:hidden;cursor:text}.tag-editor .tag-editor-tag.active{background:none !important}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default;font-size:14px}[dir=rtl] .ui-autocomplete{left:auto;right:0}.ui-front{z-index:9999}.ui-menu{list-style:none;padding:1px;margin:0;display:block;outline:none}.ui-menu .ui-menu-item a{text-decoration:none;display:block;padding:2px .4em;line-height:1.4;min-height:0}.ui-widget-content{border:1px solid #bbb;background:#fff;color:#555}.ui-widget-content a{color:#46799b}.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{background:#e0eaf1}.ui-helper-hidden-accessible{display:none}.backdrop-container{position:relative;background-color:#f5f6fa}@media(min-width: 780px){.backdrop-container{margin-left:-20px;margin-top:-20px;padding-left:20px;padding-top:20px}[dir=rtl] .backdrop-container{margin-left:0;margin-right:-20px;padding-left:0;padding-right:20px}}.slimstat-filter-link .avatar{border-radius:50px;overflow:hidden;display:inline-block;transform:translateY(3px);margin-right:2px;border:solid 1px #fff}[dir=rtl] .slimstat-filter-link .avatar{margin-right:0;margin-left:2px}.wrap.slimstat-config,.wrap.slimstat{box-sizing:border-box;overflow:hidden}.wrap.slimstat-config *,.wrap.slimstat *{box-sizing:border-box;text-decoration:none}.wrap.slimstat-config .upgrade-pro-background,.wrap.slimstat .upgrade-pro-background{width:100%}.wrap.slimstat-config select:focus,.wrap.slimstat-config input:focus,.wrap.slimstat-config textarea:focus,.wrap.slimstat select:focus,.wrap.slimstat input:focus,.wrap.slimstat textarea:focus{outline:none;box-shadow:none;border-color:#e8294c !important}.wrap.slimstat-config .button-secondary,.wrap.slimstat .button-secondary{background-color:#fff;border-radius:50px;border:1px solid #e8294c;color:#e8294c;padding:0px 12px;height:auto}.wrap.slimstat-config .button-primary,.wrap.slimstat .button-primary{background-color:#e8294c;border-radius:50px;border:none;color:#fff;padding:0px 12px;height:auto}.wrap.slimstat-config #datepicker-backdrop,.wrap.slimstat #datepicker-backdrop{width:100vw;height:100vh;top:0;left:0;position:fixed;z-index:2;display:none}[dir=rtl] .wrap.slimstat-config #datepicker-backdrop,[dir=rtl] .wrap.slimstat #datepicker-backdrop{left:auto;right:0}.wrap.slimstat-config #slimstat-date-filters a.slimstat-filter-link,.wrap.slimstat #slimstat-date-filters a.slimstat-filter-link{background-color:#f8f8f8;display:block;float:left;margin:0 5px 5px 0;padding:7px 5px;width:123px;border-radius:50px;text-align:center;color:#151515;transition:.3s all ease-out}[dir=rtl] .wrap.slimstat-config #slimstat-date-filters a.slimstat-filter-link,[dir=rtl] .wrap.slimstat #slimstat-date-filters a.slimstat-filter-link{float:right;margin:0 0 5px 5px}.wrap.slimstat-config #slimstat-date-filters a.slimstat-filter-link:hover,.wrap.slimstat #slimstat-date-filters a.slimstat-filter-link:hover{background-color:#f3f3f3}.wrap.slimstat-config #slimstat-date-filters input,.wrap.slimstat-config #slimstat-date-filters select,.wrap.slimstat #slimstat-date-filters input,.wrap.slimstat #slimstat-date-filters select{border:2px solid #f3f3f3;border-radius:50px}.wrap.slimstat-config #slimstat-date-filters input[type=submit],.wrap.slimstat #slimstat-date-filters input[type=submit]{background-color:#e8294c;border:none;border-radius:50px;transition:.3s all ease-out}.wrap.slimstat-config #slimstat-date-filters input[type=submit]:hover,.wrap.slimstat #slimstat-date-filters input[type=submit]:hover{background-color:#000}.wrap.slimstat-config .button-primary.slimstat-settings-button,.wrap.slimstat .button-primary.slimstat-settings-button{background-color:#e8294c;color:#fff;border-radius:50px;border:none;transition:.3s all ease-out;padding-left:12px;padding-right:12px}[dir=rtl] .wrap.slimstat-config .button-primary.slimstat-settings-button,[dir=rtl] .wrap.slimstat .button-primary.slimstat-settings-button{padding-left:12px;padding-right:12px}.wrap.slimstat-config .button-primary.slimstat-settings-button:hover,.wrap.slimstat .button-primary.slimstat-settings-button:hover{background-color:#000}.wrap.slimstat-config h2,.wrap.slimstat h2{color:#000;font-weight:600;font-size:24px}.wrap.slimstat-config .nav-tabs,.wrap.slimstat .nav-tabs{display:flex;background-color:#fff;border-radius:5px;overflow-y:hidden}.wrap.slimstat-config .nav-tabs .nav-tab,.wrap.slimstat .nav-tabs .nav-tab{transition:.3s all ease-out;border:none;background-color:rgba(0,0,0,0);padding:0}.wrap.slimstat-config .nav-tabs .nav-tab a,.wrap.slimstat .nav-tabs .nav-tab a{color:#151515;font-size:14px;padding:15px 15px}.wrap.slimstat-config .nav-tabs .nav-tab a:focus,.wrap.slimstat .nav-tabs .nav-tab a:focus{outline:none;box-shadow:none}.wrap.slimstat-config .nav-tabs .nav-tab a:hover,.wrap.slimstat .nav-tabs .nav-tab a:hover{color:#e8294c}.wrap.slimstat-config .nav-tabs .nav-tab.nav-tab-active,.wrap.slimstat .nav-tabs .nav-tab.nav-tab-active{border-bottom:5px solid #e8294c}.wrap.slimstat-config .nav-tabs .nav-tab.nav-tab-active a,.wrap.slimstat .nav-tabs .nav-tab.nav-tab-active a{color:#e8294c}@media(max-width: 900px){.wrap.slimstat-config .nav-tabs,.wrap.slimstat .nav-tabs{overflow-x:scroll}}.wrap.slimstat-config .form-table,.wrap.slimstat .form-table{border:none;margin-top:25px;border-radius:10px;width:100%;max-width:100%;border-collapse:separate;table-layout:auto}.wrap.slimstat-config .form-table tr:first-of-type .slimstat-options-section-header,.wrap.slimstat .form-table tr:first-of-type .slimstat-options-section-header{border-radius:10px 10px 0 0}.wrap.slimstat-config .form-table tr,.wrap.slimstat .form-table tr{border-bottom:1px solid #eee}.wrap.slimstat-config .form-table tr.alternate,.wrap.slimstat .form-table tr.alternate{background-color:#fff}.wrap.slimstat-config .form-table tr th,.wrap.slimstat .form-table tr th{padding:25px 20px;color:#151515;min-width:0;white-space:normal;word-break:break-word;overflow-wrap:anywhere}.wrap.slimstat-config .form-table tr td,.wrap.slimstat .form-table tr td{padding:25px 20px;color:#151515;min-width:0;white-space:normal;word-break:break-word;overflow-wrap:anywhere}.wrap.slimstat-config .form-table tr td input,.wrap.slimstat .form-table tr td input{border:2px solid #f3f3f3;border-radius:50px;max-width:100%}.wrap.slimstat-config .form-table tr td textarea,.wrap.slimstat .form-table tr td textarea{border:2px solid #f3f3f3;border-radius:10px;max-width:100%}.wrap.slimstat-config .form-table tr td select,.wrap.slimstat .form-table tr td select{border:2px solid #f3f3f3;border-radius:50px;max-width:100%}.wrap.slimstat-config .form-table tr td .button-primary,.wrap.slimstat .form-table tr td .button-primary{border:1px solid #e8294c;color:#e8294c;background-color:#fff;border-radius:50px;max-width:100%}.wrap.slimstat-config .form-table tr td a,.wrap.slimstat-config .form-table tr td code,.wrap.slimstat-config .form-table tr td pre,.wrap.slimstat-config .form-table tr td .description,.wrap.slimstat .form-table tr td a,.wrap.slimstat .form-table tr td code,.wrap.slimstat .form-table tr td pre,.wrap.slimstat .form-table tr td .description{white-space:normal;word-break:break-word;overflow-wrap:anywhere;max-width:100%}.wrap.slimstat-config .form-table tr td .tag-editor,.wrap.slimstat .form-table tr td .tag-editor{border-radius:6px;padding:6px 4px}.wrap.slimstat-config .form-table tr td .tag-editor .tag-editor-tag,.wrap.slimstat .form-table tr td .tag-editor .tag-editor-tag{color:#e8294c;background-color:#fff4f5}.wrap.slimstat-config .form-table tr td .tag-editor .tag-editor-delete,.wrap.slimstat .form-table tr td .tag-editor .tag-editor-delete{color:#e8294c;background-color:#fff4f5}.wrap.slimstat-config .form-table tr td .tag-editor .tag-editor-delete i::before,.wrap.slimstat .form-table tr td .tag-editor .tag-editor-delete i::before{color:#e8294c}.wrap.slimstat-config .form-table tr td .bootstrap-switch-wrapper,.wrap.slimstat .form-table tr td .bootstrap-switch-wrapper{border:2px solid #f3f3f3;border-radius:50px}.wrap.slimstat-config .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-on,.wrap.slimstat .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-on{border-radius:50px;padding:5px 0px}.wrap.slimstat-config .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-off,.wrap.slimstat .form-table tr td .bootstrap-switch-wrapper .bootstrap-switch-container span.bootstrap-switch-handle-off{border-radius:50px;padding:5px 0px;background-color:#f8f8f8;color:#151515}.wrap.slimstat-config .form-table tr .description,.wrap.slimstat .form-table tr .description{color:#616060;margin-top:7px;display:block;word-wrap:break-word;max-width:100%}.wrap.slimstat-config .slimstat-options-section-header,.wrap.slimstat .slimstat-options-section-header{background-color:#f8f8f8;padding:20px 20px;font-weight:500;color:#151515;font-size:16px}.wrap.slimstat-config #slimstat-filters,.wrap.slimstat #slimstat-filters{display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;margin-bottom:5px}.wrap.slimstat-config #slimstat-filters .form-field,.wrap.slimstat #slimstat-filters .form-field{position:relative}.wrap.slimstat-config #slimstat-filters select,.wrap.slimstat #slimstat-filters select{border:1px solid #eee;border-radius:5px;display:block;width:100%;margin:0;appearance:none;-webkit-appearance:none;-moz-appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolyline points='4 6 8 10 12 6' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 8px center;padding-right:32px}.wrap.slimstat-config #slimstat-filters input,.wrap.slimstat #slimstat-filters input{border:1px solid #eee;border-radius:5px;display:block;width:100%;margin:0;max-width:95%}.wrap.slimstat-config #slimstat-filters input[type=submit],.wrap.slimstat #slimstat-filters input[type=submit]{border:1px solid #e8294c;background-color:#fff;color:#e8294c;width:auto}[dir=rtl] .wrap.slimstat-config #slimstat-filters input[type=submit],[dir=rtl] .wrap.slimstat #slimstat-filters input[type=submit]{margin-left:0;margin-right:0}@media(max-width: 400px){.wrap.slimstat-config #slimstat-filters,.wrap.slimstat #slimstat-filters{margin-bottom:12px}.wrap.slimstat-config #slimstat-filters .form-field,.wrap.slimstat #slimstat-filters .form-field{flex:0 0 50%;margin-bottom:10px}}.wrap.slimstat-config .meta-box-sortables form,.wrap.slimstat .meta-box-sortables form{display:none}.wrap.slimstat-config .meta-box-sortables .postbox,.wrap.slimstat .meta-box-sortables .postbox{border:solid 1px #f0f0f0;border-radius:10px;margin-right:.5%}[dir=rtl] .wrap.slimstat-config .meta-box-sortables .postbox,[dir=rtl] .wrap.slimstat .meta-box-sortables .postbox{margin-right:0;margin-left:.5%}.wrap.slimstat-config .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner,.wrap.slimstat .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner{display:inline-block;float:none;margin-right:3px;cursor:help}.wrap.slimstat-config .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner::before,.wrap.slimstat .meta-box-sortables .postbox .slimScrollDiv .slimstat-tooltip-trigger.corner::before{color:#aaa;transform:scaleX(-1);display:block}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons{padding:15px 10px 0 10px;display:flex;align-items:center;justify-content:flex-end;float:right}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons .dashicons,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons .dashicons{width:auto;height:auto}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a:focus,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a:focus{box-shadow:none}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.refresh,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.refresh{padding:5px 5px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.refresh::before,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.refresh::before{transform:translateY(-1px);display:block;color:#616060;transition:.3s all ease-out}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download{padding:5px 5px;border-radius:5px;font-weight:700;font-size:12px;line-height:100%;letter-spacing:0px;color:#676e74;transition:.3s all ease-out;cursor:pointer;position:relative;top:0px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover{color:#202224}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover span.dashicons::before,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download:not(.is-not-pro):hover span.dashicons::before{color:#202224}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before,.wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before{color:#676e74;font-size:14px;margin-right:2px;display:block;transform:translateY(1px);transition:.3s all ease-out}[dir=rtl] .wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons,[dir=rtl] .wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons{float:left}[dir=rtl] .wrap.slimstat-config .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before,[dir=rtl] .wrap.slimstat .meta-box-sortables .postbox .slimstat-header-buttons a.slimstat-font-download span.dashicons::before{margin-right:0px;margin-left:3px}.wrap.slimstat-config .meta-box-sortables .postbox h3,.wrap.slimstat .meta-box-sortables .postbox h3{background-color:#fff;color:#202224;padding:18px 15px;border:none;font-size:16px;font-weight:bold;letter-spacing:-0.43px}.wrap.slimstat-config .meta-box-sortables .postbox h3 .header-tooltip,.wrap.slimstat .meta-box-sortables .postbox h3 .header-tooltip{margin:0px 5px;display:inline-block;transform:translateY(3px)}.wrap.slimstat-config .meta-box-sortables .postbox h3 .header-tooltip:hover svg path,.wrap.slimstat .meta-box-sortables .postbox h3 .header-tooltip:hover svg path{fill:#202224}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-browser-icon,.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-flag-icon,.wrap.slimstat-config .meta-box-sortables .postbox span.slimstat-flag-container,.wrap.slimstat .meta-box-sortables .postbox .slimstat-browser-icon,.wrap.slimstat .meta-box-sortables .postbox .slimstat-flag-icon,.wrap.slimstat .meta-box-sortables .postbox span.slimstat-flag-container{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-author-link img,.wrap.slimstat .meta-box-sortables .postbox .slimstat-author-link img{position:relative !important;width:18px;height:auto !important;float:left !important;margin:0px 10px 0px 0px;transform:translateY(1px);border-radius:60px}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap,.wrap.slimstat .meta-box-sortables .postbox .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap{z-index:0 !important;position:absolute;display:block;width:calc(100% - 30px);height:100%;top:0;left:0;margin:0px 15px;box-sizing:border-box}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-tooltip-trigger *,.wrap.slimstat .meta-box-sortables .postbox .slimstat-tooltip-trigger *{z-index:2 !important;position:relative}.wrap.slimstat-config .meta-box-sortables .postbox .slimstat-tooltip-trigger a,.wrap.slimstat .meta-box-sortables .postbox .slimstat-tooltip-trigger a{max-width:80%;display:inline-block;color:#202224;font-weight:600;vertical-align:top;font-size:12px}.wrap.slimstat-config p.slimstat-tooltip-trigger,.wrap.slimstat p.slimstat-tooltip-trigger{border:none !important;margin-bottom:8px}.wrap.slimstat-config .pagination,.wrap.slimstat .pagination{color:#6f7478;position:absolute;left:0;bottom:0;background-color:#fff;z-index:2;width:100%;padding:10px 15px}.wrap.slimstat-config a.refresh.slimstat-font-angle-double-right,.wrap.slimstat-config a.refresh.slimstat-font-angle-right,.wrap.slimstat-config a.refresh.slimstat-font-angle-double-left,.wrap.slimstat-config a.refresh.slimstat-font-angle-left,.wrap.slimstat a.refresh.slimstat-font-angle-double-right,.wrap.slimstat a.refresh.slimstat-font-angle-right,.wrap.slimstat a.refresh.slimstat-font-angle-double-left,.wrap.slimstat a.refresh.slimstat-font-angle-left{width:24px;border:solid 1px #dadce0;height:24px;padding:0;border-radius:50%;text-align:center;vertical-align:middle}.wrap.slimstat-config a.refresh.slimstat-font-angle-double-right:before,.wrap.slimstat-config a.refresh.slimstat-font-angle-right:before,.wrap.slimstat-config a.refresh.slimstat-font-angle-double-left:before,.wrap.slimstat-config a.refresh.slimstat-font-angle-left:before,.wrap.slimstat a.refresh.slimstat-font-angle-double-right:before,.wrap.slimstat a.refresh.slimstat-font-angle-right:before,.wrap.slimstat a.refresh.slimstat-font-angle-double-left:before,.wrap.slimstat a.refresh.slimstat-font-angle-left:before{color:#676e74 !important;font-size:13px}.wrap.slimstat-config span.slimstat-tooltip-bar,.wrap.slimstat span.slimstat-tooltip-bar{display:block;height:100%;max-width:100% !important;position:absolute !important;top:0;left:0;background-color:var(--box-bar-color);border-radius:6px}.wrap.slimstat-config .slimScrollDiv p,.wrap.slimstat .slimScrollDiv p{padding:10px 25px}.wrap.slimstat-config .slimScrollDiv p.header,.wrap.slimstat .slimScrollDiv p.header{border:none}.wrap.slimstat-config .slimScrollDiv code,.wrap.slimstat .slimScrollDiv code{border-radius:3px;background-color:#fff;padding:3px 7px}.wrap.slimstat table.widefat{border:none}.wrap.slimstat table.widefat tbody tr:nth-child(even){background-color:#f8f8f8}.wrap.slimstat table.widefat tbody tr td{vertical-align:middle}.wrap.slimstat table.widefat thead{background-color:#fff4f5}.wrap.slimstat table.widefat thead th{white-space:nowrap;border:none;border-top:1px solid #e8294c;border-bottom:1px solid #e8294c;color:#000;padding:8px 10px;vertical-align:middle}.wrap.slimstat table.widefat thead th a{color:#e8294c}.slimstat-header{padding:30px 25px;margin-bottom:20px;background-color:#2b2b2b;display:flex;align-items:center;justify-content:flex-start;position:relative;margin-left:-20px;box-sizing:border-box}[dir=rtl] .slimstat-header{margin-left:0;margin-right:-20px;justify-content:flex-end}.slimstat-header .logo{width:180px;height:auto}.slimstat-header .vr-line{background-color:#9a9a9a;margin:0 40px;display:block;width:1px;height:40px}.slimstat-header .go-pro{text-align:left}[dir=rtl] .slimstat-header .go-pro{text-align:right}.slimstat-header .go-pro a{display:flex;align-items:center;justify-content:flex-start;margin-bottom:3px;color:#fff;text-decoration:none;font-size:15px;font-weight:400;cursor:pointer}[dir=rtl] .slimstat-header .go-pro a{justify-content:flex-end}.slimstat-header .go-pro a .icon{background:url("../images/white-right-chevron.png") no-repeat center center/contain;width:10px;height:10px;margin-left:5px;display:block}[dir=rtl] .slimstat-header .go-pro a .icon{margin-left:0;margin-right:5px;transform:scaleX(-1)}.slimstat-header .go-pro p{text-align:left;font-weight:300;color:#fff;margin:0}[dir=rtl] .slimstat-header .go-pro p{text-align:right}.slimstat-header .pro-badge{display:flex;align-items:center;justify-content:flex-start;position:absolute;right:20px;bottom:20px;color:#fff}[dir=rtl] .slimstat-header .pro-badge{right:auto;left:20px;justify-content:flex-end}.slimstat-header .pro-badge p{margin:0}.slimstat-header .pro-badge .icon{background:url("../images/pro-badge.png") no-repeat center center/contain;width:16px;height:16px;margin-right:7px;display:block}[dir=rtl] .slimstat-header .pro-badge .icon{margin-right:0;margin-left:7px}[dir=rtl] .slimstat-header{margin-right:-20px;margin-left:0}[dir=rtl] .slimstat-header .pro-badge{right:auto;left:20px}[dir=rtl] .slimstat-header .pro-badge .icon{margin-left:7px;margin-right:0}[dir=rtl] .slimstat-header .go-pro a .icon{background:url(../images/white-right-chevron.png) no-repeat center center/contain;margin-left:0px;margin-right:5px;transform:scaleX(-1)}[dir=rtl] .slimstat-header .go-pro p{text-align:right}@media(max-width: 500px){.slimstat-header{display:block}.slimstat-header .vr-line{margin:20px 0;width:100%;height:1px}.slimstat-header .go-pro{margin-top:20px}.slimstat-header .pro-badge{right:10px}[dir=rtl] .slimstat-header .pro-badge{left:10px;right:auto}}.slimstat-layout{position:relative}.slimstat-layout .postbox-container{border:solid 1px #f0f0f0}.slimstat-layout .postbox-container:has(.ui-sortable-placeholder){border:dashed 1px #ff8080}.slimstat-layout .postbox-container .meta-box-sortables,.slimstat-layout .postbox-container h2{background-color:#fff !important;color:#202224;padding:18px 15px;font-size:16px !important;font-weight:bold !important;letter-spacing:-0.43px}.slimstat-layout .postbox{border-radius:8px !important;box-shadow:none !important;background-color:#fff}.slimstat-layout .postbox h3{font-weight:400 !important;padding:12px 15px !important;background-color:#f1f1f1 !important;font-size:14px !important}#dashboard-widgets-wrap .postbox .slimstat-tooltip-trigger.corner{display:inline-block;float:none;margin-right:3px;cursor:help}#dashboard-widgets-wrap .postbox .slimstat-tooltip-trigger.corner::before{color:#aaa;display:block;transform:scaleX(-1)}.slimstat-pro-modal-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(3px);background-color:hsla(0,0%,100%,.5);z-index:20;display:none}.slimstat-pro-modal{position:fixed;z-index:30;transform:translateX(-50%);background-color:#fff;border-radius:25px;box-shadow:0px 4px 80px rgba(0,0,0,.15);padding:40px 4%;text-align:center;box-sizing:border-box;max-width:720px;margin-top:100px;left:50%;transform:translate(-50%, 0);top:45%;transform:translateX(-50%) translateY(-50%)}[dir=rtl] .slimstat-pro-modal{transform:translateX(50%) translateY(-50%)}.slimstat-pro-modal *{margin:0;padding:0;box-sizing:border-box}.slimstat-pro-modal #slimstat-pro-modal-close{position:absolute;cursor:pointer;top:30px;right:10%;width:30px;height:30px;opacity:.3;transition:.3s all ease-out;background:url("data:image/svg+xml,%3Csvg width='33' height='32' viewBox='0 0 33 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.6172 31.1641C24.9015 31.1641 31.6172 24.4483 31.6172 16.1641C31.6172 7.87979 24.9015 1.16406 16.6172 1.16406C8.33292 1.16406 1.61719 7.87979 1.61719 16.1641C1.61719 24.4483 8.33292 31.1641 16.6172 31.1641Z' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21.1172 11.6641L12.1172 20.6641' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12.1172 11.6641L21.1172 20.6641' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center/contain}[dir=rtl] .slimstat-pro-modal #slimstat-pro-modal-close{right:auto;left:10%}.slimstat-pro-modal #slimstat-pro-modal-close:hover{opacity:1}.slimstat-pro-modal h2 .subtitle{font-size:18px;margin-bottom:12px;color:#151515;display:block}.slimstat-pro-modal h2 .title{color:#e8294c;font-size:22px;font-weight:700;margin-bottom:25px;display:block}.slimstat-pro-modal .description{color:#616060;font-weight:400;font-size:16px;margin-bottom:25px}.slimstat-pro-modal .scroller{max-height:45vh;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#333 #f5f5f5;scrollbar-face-color:#f5f5f5;scrollbar-arrow-color:#333;scrollbar-track-color:#f5f5f5;scrollbar-shadow-color:#333;scrollbar-highlight-color:#333;scrollbar-3dlight-color:#333;scrollbar-darkshadow-color:#333}.slimstat-pro-modal .scroller::-webkit-scrollbar{width:5px}.slimstat-pro-modal .scroller::-webkit-scrollbar-thumb{background-color:#333;border-radius:10px}.slimstat-pro-modal .scroller::-webkit-scrollbar-track{background-color:#f5f5f5}.slimstat-pro-modal .features-flex-box{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap}.slimstat-pro-modal .features-flex-box .feature-item{flex:0 0 45%;border-bottom:1px solid #f3f3f3;padding:15px 0;display:flex;align-items:center;justify-content:space-between}.slimstat-pro-modal .features-flex-box .feature-item h6{color:#151515;font-weight:500;font-size:16px}.slimstat-pro-modal .features-flex-box .feature-item h6 .icon{transform:translateY(6px);width:20px;height:20px;margin-right:7px;display:inline-block;background:url("data:image/svg+xml,%3Csvg width='18' height='17' viewBox='0 0 18 17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.4463 7.90039V8.57696C16.4454 10.1628 15.9319 11.7058 14.9823 12.976C14.0328 14.2461 12.6982 15.1753 11.1774 15.625C9.65665 16.0746 8.03129 16.0206 6.54373 15.471C5.05617 14.9215 3.78612 13.9057 2.92298 12.5754C2.05985 11.245 1.64988 9.67128 1.75422 8.08889C1.85857 6.50649 2.47163 5.00021 3.50198 3.79471C4.53233 2.5892 5.92476 1.74905 7.4716 1.39956C9.01844 1.05007 10.6368 1.20997 12.0854 1.8554' stroke='%23E8294C' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M16.445 2.69531L9.09097 10.0567L6.88477 7.85047' stroke='%23E8294C' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center/contain}[dir=rtl] .slimstat-pro-modal .features-flex-box .feature-item h6 .icon{margin-right:0;margin-left:7px}.slimstat-pro-modal .features-flex-box .feature-item .more-info-icon{display:block;width:20px;height:20px;margin-left:3px;background:url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.20996 15.4014C12.1434 15.4014 15.332 12.2127 15.332 8.2793C15.332 4.34589 12.1434 1.15723 8.20996 1.15723C4.27655 1.15723 1.08789 4.34589 1.08789 8.2793C1.08789 12.2127 4.27655 15.4014 8.20996 15.4014Z' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M6.13672 6.14245C6.30416 5.66645 6.63466 5.26508 7.06968 5.00942C7.5047 4.75376 8.01616 4.6603 8.51348 4.7456C9.0108 4.83091 9.46188 5.08946 9.78683 5.47548C10.1118 5.8615 10.2896 6.35007 10.2889 6.85465C10.2889 8.27907 8.15226 8.99127 8.15226 8.99127' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8.21094 11.8398H8.21769' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center/contain}[dir=rtl] .slimstat-pro-modal .features-flex-box .feature-item .more-info-icon{margin-left:0;margin-right:3px}.slimstat-pro-modal .go-pro-button{display:inline-block;padding:15px 10px;width:250px;border-radius:10px;font-size:16px;font-weight:500;color:#fff;text-align:center;background-color:#e8294c;text-decoration:none;margin-top:35px;transition:.3s all ease-out}.slimstat-pro-modal .go-pro-button:hover{background-color:#000}[dir=rtl] .slimstat-pro-modal .features-flex-box .feature-item h6 .icon{margin-left:7px;margin-right:0}[dir=rtl] .slimstat-pro-modal #slimstat-pro-modal-close{left:10%;right:auto}@media(max-width: 780px){.slimstat-pro-modal{border-radius:15px;padding:20px 20px;width:95%}.slimstat-pro-modal #slimstat-pro-modal-close{top:20px;right:20px}[dir=rtl] .slimstat-pro-modal #slimstat-pro-modal-close{right:auto;left:20px}.slimstat-pro-modal h2 .subtitle{font-size:15px}.slimstat-pro-modal h2 .title{font-size:18px}.slimstat-pro-modal .features-flex-box .feature-item{flex:0 0 100%}.slimstat-pro-modal .features-flex-box .feature-item h6 .icon{transform:translateY(4px)}[dir=rtl] .slimstat-pro-modal .features-flex-box .feature-item h6 .icon{margin-right:0;margin-left:7px}}.slimstat-searchable-select{position:relative;display:inline-block;width:100%}.slimstat-searchable-select .slimstat-select-wrapper{width:180px;margin-right:5px;position:relative;border:1px solid #eee;border-radius:5px;background:#fff;cursor:pointer;min-height:32px}.slimstat-searchable-select .slimstat-select-wrapper:hover{border-color:#e8294c}.slimstat-searchable-select .slimstat-select-wrapper.slimstat-select-open{border-color:#e8294c;border-bottom-left-radius:0;border-bottom-right-radius:0}.slimstat-searchable-select .slimstat-select-display{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;min-height:20px;font-size:14px;color:#555}.slimstat-searchable-select .slimstat-select-display.slimstat-placeholder{color:#999}.slimstat-searchable-select .slimstat-select-display .slimstat-select-text{flex:1;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.slimstat-searchable-select .slimstat-select-display .slimstat-select-arrow{flex-shrink:0;margin-left:8px;transition:transform .2s ease;display:flex;align-items:center;justify-content:center;width:16px;height:16px;color:#666}.slimstat-searchable-select .slimstat-select-display .slimstat-select-arrow svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.slimstat-searchable-select .slimstat-select-wrapper.slimstat-select-open .slimstat-select-arrow{transform:rotate(180deg)}.slimstat-searchable-select .slimstat-select-dropdown{position:absolute;top:100%;left:0;right:0;background:#fff;border:1px solid #e8294c;border-top:none;border-radius:0 0 5px 5px;box-shadow:0 2px 8px rgba(0,0,0,.1);z-index:1000;max-height:200px;overflow:hidden}.slimstat-searchable-select .slimstat-select-search{position:relative;padding:8px}.slimstat-searchable-select .slimstat-select-search input{width:100%;padding:6px 12px;border:1px solid #ddd;border-radius:3px;font-size:14px;outline:none}.slimstat-searchable-select .slimstat-select-search input:focus{border-color:#e8294c;box-shadow:0 0 0 1px #e8294c}.slimstat-searchable-select .slimstat-select-options{max-height:150px;overflow-y:auto}.slimstat-searchable-select .slimstat-select-option{display:flex;width:100%;padding:8px 12px;border:none;background:none;text-align:left;font-size:14px;color:#555;cursor:pointer;transition:background-color .2s ease}.slimstat-searchable-select .slimstat-select-option:hover{background-color:#f5f5f5}.slimstat-searchable-select .slimstat-select-option.slimstat-selected{background-color:#e8294c;color:#fff}.slimstat-searchable-select .slimstat-select-option.slimstat-hidden{display:none}.slimstat-searchable-select .slimstat-select-no-results{padding:12px;text-align:center;color:#999;font-style:italic;font-size:14px}.slimstat-searchable-select .slimstat-select-loading{padding:12px;text-align:center;color:#666;font-size:14px}.slimstat-searchable-select .slimstat-select-loading:before{content:"\\f463";font-family:dashicons;display:inline-block;animation:spin 1s linear infinite;margin-right:5px}.slimstat-option-icon{display:inline-block;vertical-align:middle;margin-right:5px;flex-shrink:0;border-radius:3px;object-fit:cover}.slimstat-select-option{align-items:center}.slimstat-select-option .slimstat-option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.slimstat-select-text .slimstat-option-icon{margin-right:5px;vertical-align:middle;display:inline-block}.rtl .slimstat-option-icon{margin-right:0;margin-left:5px}.rtl .slimstat-select-text .slimstat-option-icon{margin-right:0;margin-left:5px} Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: admin.css.map Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: daterangepicker Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: header-modern.css Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: header-notifications.css Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: live-analytics.css Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: migration.css Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/css: notifications.css @@ -455,3 +455,85 @@ font-weight: normal; font-style: italic; } + +/* Bar Chart Styles */ +.slimstat-chart-wrap .slimstat-postbox-chart--canvas { + transition: all 0.3s ease; +} + +/* Bar chart specific styling */ +.chart-bar .slimstat-postbox-chart--canvas { + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +/* Bar chart hover effects */ +.chart-bar .slimstat-postbox-chart--canvas:hover { + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +/* Legend styling for bar charts */ +.chart-bar .slimstat-postbox-chart--item { + padding: 8px 12px; + border-radius: 6px; + transition: all 0.2s ease; +} + +.chart-bar .slimstat-postbox-chart--item:hover { + background-color: rgba(0, 0, 0, 0.05); + transform: translateY(-1px); +} + +.chart-bar .slimstat-postbox-chart--item--color { + border-radius: 4px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); +} + +/* Animation for bar charts */ +@keyframes barChartLoad { + from { + opacity: 0; + transform: scaleY(0); + transform-origin: bottom; + } + to { + opacity: 1; + transform: scaleY(1); + } +} + +.chart-bar .slimstat-postbox-chart--canvas { + animation: barChartLoad 0.6s ease-out; +} + +/* Responsive bar chart adjustments */ +@media (max-width: 768px) { + .chart-bar .slimstat-postbox-chart--canvas { + height: 200px !important; + } + + .chart-bar .slimstat-postbox-chart--items { + flex-direction: column; + gap: 8px; + } + + .chart-bar .slimstat-postbox-chart--item { + padding: 6px 10px; + font-size: 13px; + } +} + +/* Bar chart loading state */ +.chart-bar.loading .slimstat-postbox-chart--canvas { + opacity: 0.6; + filter: blur(1px); +} + +/* Bar chart peak highlighting */ +.chart-bar .bar-peak { + background: linear-gradient(135deg, #e8294c 0%, #ff6b7d 100%) !important; + box-shadow: 0 4px 12px rgba(232, 41, 76, 0.3) !important; + transform: scaleY(1.1); + z-index: 10; +} Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/images: email report.PNG @@ -1,4 +1,5 @@ <svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-xx" viewBox="0 0 640 480"> - <path fill="#fff" fill-rule="evenodd" stroke="#adb5bd" stroke-width="1.1" d="M.5.5h638.9v478.9H.5z"/> - <path fill="none" stroke="#adb5bd" stroke-width="1.1" d="m.5.5 639 479m0-479-639 479"/> + <rect width="640" height="480" fill="#f8f9fa" stroke="#adb5bd" stroke-width="1.1"/> + <circle cx="320" cy="240" r="150" fill="gray"/> + <text x="50%" y="55%" text-anchor="middle" dominant-baseline="middle" font-size="220" font-weight="700" font-family="system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif" fill="#ffffff">?</text> </svg> Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets: img Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/js: adminbar-realtime.js @@ -6,6 +6,34 @@ }; } +// Clear Cache Button Handler +jQuery(document).on("click", "#slimstat-clear-cache", function (e) { + e.preventDefault(); + var $btn = jQuery(this); + $btn.prop("disabled", true); + $btn.after('<span class="loading" style="vertical-align: middle; position: relative; top: 3px;"> <i class="slimstat-font-spin4 animate-spin"></i> </span>'); + jQuery + .ajax({ + method: "POST", + url: ajaxurl, + data: { + action: "slimstat_clear_cache", + security: typeof SlimStatAdminParams.clear_cache_nonce !== "undefined" ? SlimStatAdminParams.clear_cache_nonce : "", + }, + dataType: "json", + }) + .done(function (result) { + alert(result.data || "Cache cleared!"); + }) + .fail(function (xhr) { + alert("Cache clear failed!"); + }) + .always(function () { + $btn.prop("disabled", false); + $btn.next(".loading").remove(); + }); +}); + // ----- TABLE OF CONTENTS ----------------------------------------------------------- // // 1. Data Refresh @@ -15,9 +43,271 @@ // 5. Miscellaneous // 6. Init Third-party Libraries // 7. Init SlimStat Pro Modal +// 8. Conditional Fields System // // ----------------------------------------------------------------------------------- +/** + * Conditional Fields System + * + * A dynamic system for showing/hiding form fields based on other field values. + * + * Usage: + * Add data attributes to fields that should be conditionally shown/hidden: + * - data-conditional-field="field_id" - The field ID that controls visibility + * - data-conditional-value="value" - The value(s) that should show this field (comma-separated for multiple) + * - data-conditional-type="equals|not_equals|checked|not_checked|in|not_in" - Comparison type + * + * Example: + * <tr data-conditional-field="gdpr_enabled" data-conditional-type="checked"> + * <td>This row shows when gdpr_enabled is checked</td> + * </tr> + * + * <tr data-conditional-field="consent_integration" data-conditional-value="slimstat_banner,wp_consent_api" data-conditional-type="in"> + * <td>This row shows when consent_integration is either 'slimstat_banner' or 'wp_consent_api'</td> + * </tr> + */ +(function ($) { + "use strict"; + + var ConditionalFields = { + /** + * Get the current value of a field + * @param {string} fieldId - The field ID + * @returns {string|boolean} The current value + */ + getFieldValue: function (fieldId) { + var $field = $("#" + fieldId); + + if ($field.length === 0) { + return null; + } + + // Checkbox/toggle + if ($field.is(":checkbox") || $field.hasClass("slimstat-checkbox-toggle")) { + return $field.is(":checked"); + } + + // Select + if ($field.is("select")) { + return $field.val() || ""; + } + + // Radio buttons + if ($field.is(":radio")) { + return $('input[name="' + $field.attr("name") + '"]:checked').val() || ""; + } + + // Text/Number inputs + return $field.val() || ""; + }, + + /** + * Check a single condition + * @param {string} fieldId - The field ID + * @param {string} conditionType - The condition type + * @param {string} expectedValue - The expected value + * @returns {boolean} True if condition is met + */ + checkSingleCondition: function (fieldId, conditionType, expectedValue) { + var currentValue = this.getFieldValue(fieldId); + + if (currentValue === null) { + return false; + } + + switch (conditionType) { + case "checked": + return currentValue === true; + + case "not_checked": + return currentValue === false; + + case "equals": + return String(currentValue) === String(expectedValue); + + case "not_equals": + return String(currentValue) !== String(expectedValue); + + case "in": + if (!expectedValue) { + return false; + } + var values = String(expectedValue) + .split(",") + .map(function (v) { + return String(v).trim(); + }); + return values.indexOf(String(currentValue)) !== -1; + + case "not_in": + if (!expectedValue) { + return true; + } + var notInValues = String(expectedValue) + .split(",") + .map(function (v) { + return String(v).trim(); + }); + return notInValues.indexOf(String(currentValue)) === -1; + + case "empty": + return !currentValue || String(currentValue).trim() === ""; + + case "not_empty": + return currentValue && String(currentValue).trim() !== ""; + + default: + return false; + } + }, + + /** + * Check if a condition is met (supports multiple conditions with AND logic) + * @param {jQuery} $element - The element with conditional attributes + * @returns {boolean} True if condition is met + */ + checkCondition: function ($element) { + // Support multiple conditions (AND logic) + var fields = $element.data("conditional-field"); + var types = $element.data("conditional-type") || "equals"; + var values = $element.data("conditional-value"); + + // If multiple fields are specified (comma-separated), check all of them + if (fields && fields.indexOf(",") !== -1) { + var fieldArray = fields.split(",").map(function (f) { + return f.trim(); + }); + var typeArray = types.split(",").map(function (t) { + return t.trim(); + }); + var valueArray = values + ? values.split("|||").map(function (v) { + return v.trim(); + }) + : []; + + // Check all conditions (AND logic) + for (var i = 0; i < fieldArray.length; i++) { + var fieldId = fieldArray[i]; + var conditionType = typeArray[i] || "equals"; + var expectedValue = valueArray[i] || ""; + + if (!this.checkSingleCondition(fieldId, conditionType, expectedValue)) { + return false; + } + } + return true; + } + + // Single condition (backward compatibility) + var fieldId = fields; + var conditionType = types; + var expectedValue = values; + + return this.checkSingleCondition(fieldId, conditionType, expectedValue); + }, + + /** + * Update visibility of a conditional element + * @param {jQuery} $element - The element to show/hide + */ + updateVisibility: function ($element) { + var conditionMet = this.checkCondition($element); + var $row = $element.closest("tr"); + + if (conditionMet) { + $row.removeClass("hidden").show(); + } else { + $row.hide(); + } + }, + + /** + * Update all conditional fields based on a trigger field + * @param {string} triggerFieldId - The field ID that triggered the update + */ + updateAllConditionals: function (triggerFieldId) { + var self = this; + // Update fields that directly depend on this trigger + $('[data-conditional-field="' + triggerFieldId + '"]').each(function () { + self.updateVisibility($(this)); + }); + // Update fields that have multiple conditions (including this trigger) + $('[data-conditional-field*="' + triggerFieldId + '"]').each(function () { + var $element = $(this); + var fields = $element.data("conditional-field"); + // Only update if this field is part of a multi-condition + if (fields && fields.indexOf(",") !== -1 && fields.indexOf(triggerFieldId) !== -1) { + self.updateVisibility($element); + } + }); + }, + + /** + * Initialize all conditional fields + */ + init: function () { + var self = this; + var processedFields = {}; // Track which fields already have listeners + + // Find all elements with conditional attributes + $("[data-conditional-field]").each(function () { + var $element = $(this); + var fields = $element.data("conditional-field"); + + // Update initial visibility + self.updateVisibility($element); + + // Handle multi-condition (comma-separated fields) + var fieldArray = []; + if (fields && fields.indexOf(",") !== -1) { + fieldArray = fields.split(",").map(function (f) { + return f.trim(); + }); + } else { + fieldArray = [fields]; + } + + // Set up event listeners for each trigger field + fieldArray.forEach(function (fieldId) { + if (!fieldId || processedFields[fieldId]) { + return; // Skip if already processed + } + processedFields[fieldId] = true; + + var $triggerField = $("#" + fieldId); + if ($triggerField.length > 0) { + // Remove existing listeners to avoid duplicates + $triggerField.off("change.conditionalFields"); + + // Add change listener + $triggerField.on("change.conditionalFields", function () { + self.updateAllConditionals(fieldId); + }); + + // For checkbox toggles, also listen to switchChange event + if ($triggerField.hasClass("slimstat-checkbox-toggle")) { + $triggerField.off("switchChange.bootstrapSwitch.conditionalFields"); + $triggerField.on("switchChange.bootstrapSwitch.conditionalFields", function () { + self.updateAllConditionals(fieldId); + }); + } + } + }); + }); + }, + }; + + // Initialize on document ready + jQuery(function () { + ConditionalFields.init(); + }); + + // Expose to global scope for manual initialization if needed + window.SlimStatConditionalFields = ConditionalFields; +})(jQuery); + jQuery(function () { // Show Tracking Request Method only when Tracking Mode = Client function toggleTrackingRequestMethod() { @@ -35,27 +325,32 @@ jQuery(document).on("change", toggleSelector, toggleTrackingRequestMethod); jQuery(document).on("switchChange.bootstrapSwitch", toggleSelector, toggleTrackingRequestMethod); - var licenseType = jQuery("#enable_maxmind"); - if (licenseType.val() !== "on") { - jQuery("#maxmind_license_key").closest("tr").css("display", "none"); - jQuery("#maxmind_user_id").closest("tr").css("display", "none"); + // Geolocation provider-based UI toggles + function toggleGeoUi() { + var provider = jQuery("#geolocation_provider").val(); + var $licenseRow = jQuery("#maxmind_license_key").closest("tr"); + var $dbActionsRow = jQuery("#slimstat-update-geoip-database").length ? jQuery("#slimstat-update-geoip-database").closest("tr") : jQuery(); + + if (provider === "maxmind") { + $licenseRow.css("display", "table-row"); + $dbActionsRow.css("display", "table-row"); + } else if (provider === "dbip") { + $licenseRow.css("display", "none"); + $dbActionsRow.css("display", "table-row"); + } else if (provider === "cloudflare") { + $licenseRow.css("display", "none"); + $dbActionsRow.css("display", "none"); + } } + // Initialize and bind change + toggleGeoUi(); + jQuery(document).on("change", "#geolocation_provider", toggleGeoUi); // ----- BEGIN: ACCESS LOG ------------------------------------------------------- // SlimStatAdmin.access_log_count_down(); - jQuery("#enable_maxmind").on("change", function (e) { - var value = e.target.value; - if (value == "on") { - jQuery("#maxmind_user_id").closest("tr").css("display", "table-row"); - jQuery("#maxmind_license_key").closest("tr").css("display", "table-row"); - } - if (value == "no") { - jQuery("#maxmind_user_id").closest("tr").css("display", "none"); - jQuery("#maxmind_license_key").closest("tr").css("display", "none"); - } - }); + // remove legacy enable_maxmind toggle handler (migrated to provider-based) // GeoIP Database Manually Update jQuery("#slimstat-update-geoip-database").on("click", function (e) { @@ -182,12 +477,503 @@ // ----- BEGIN: FILTERS ---------------------------------------------------------- // + // Custom Searchable Select Component + // Make all texts translatable using wp.i18n if available, with fallbacks + const __ = typeof window.wp !== "undefined" && wp.i18n && typeof wp.i18n.__ === "function" ? wp.i18n.__ : (s) => s; + class SlimStatSearchableSelect { + constructor(element, options = {}) { + // Validate element exists + if (!element) { + throw new Error("SlimStatSearchableSelect: element is required"); + } + + this.element = element; + this.options = { + placeholder: __("Select value...", "wp-slimstat"), + searchPlaceholder: __("Search...", "wp-slimstat"), + noResultsText: __("No results found", "wp-slimstat"), + loadingText: __("Loading...", "wp-slimstat"), + allowClear: true, + ...options, + }; + + this.selectedValue = ""; + this.selectedText = ""; + this.selectedOption = null; + this.isOpen = false; + this.filteredOptions = []; + this.allOptions = []; + + this.init(); + } + + init() { + this.createWrapper(); + this.bindEvents(); + } + + createWrapper() { + // Create wrapper structure + this.wrapper = document.createElement("div"); + this.wrapper.className = "slimstat-searchable-select"; + + this.selectWrapper = document.createElement("div"); + this.selectWrapper.className = "slimstat-select-wrapper"; + + this.display = document.createElement("div"); + this.display.className = "slimstat-select-display slimstat-placeholder"; + // Create elements safely to prevent XSS + const textSpan = document.createElement("span"); + textSpan.className = "slimstat-select-text"; + textSpan.textContent = this.options.placeholder; + + const arrowSpan = document.createElement('span'); + arrowSpan.className = 'slimstat-select-arrow'; + arrowSpan.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><polyline points="4 6 8 10 12 6"></polyline></svg>'; + + this.display.appendChild(textSpan); + this.display.appendChild(arrowSpan); + + this.dropdown = document.createElement("div"); + this.dropdown.className = "slimstat-select-dropdown"; + this.dropdown.style.display = "none"; + + this.searchContainer = document.createElement("div"); + this.searchContainer.className = "slimstat-select-search"; + // Create search input safely to prevent XSS + const searchInput = document.createElement("input"); + searchInput.type = "text"; + searchInput.placeholder = this.options.searchPlaceholder; + this.searchContainer.appendChild(searchInput); + + this.optionsContainer = document.createElement("div"); + this.optionsContainer.className = "slimstat-select-options"; + + this.dropdown.appendChild(this.searchContainer); + this.dropdown.appendChild(this.optionsContainer); + + this.selectWrapper.appendChild(this.display); + this.selectWrapper.appendChild(this.dropdown); + this.wrapper.appendChild(this.selectWrapper); + + // Ensure the original input has the name attribute before hiding + if (!this.element.hasAttribute("name")) { + this.element.setAttribute("name", "v"); + } + + // Insert wrapper before original element + this.element.parentNode.insertBefore(this.wrapper, this.element); + + // Move the original element inside the wrapper to keep it in the form + // but keep it hidden and maintain it as part of the form submission + this.wrapper.appendChild(this.element); + this.element.style.display = "none"; + } + + bindEvents() { + // Display click to toggle dropdown + this.display.addEventListener("click", (e) => { + e.stopPropagation(); + this.toggle(); + }); + + // Search input + const searchInput = this.searchContainer.querySelector("input"); + searchInput.addEventListener("input", (e) => { + this.filterOptions(e.target.value); + }); + + searchInput.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + this.close(); + } + }); + + // Click outside to close + document.addEventListener("click", (e) => { + if (!this.wrapper.contains(e.target)) { + this.close(); + } + }); + + // Prevent dropdown from closing when clicking inside + this.dropdown.addEventListener("click", (e) => { + e.stopPropagation(); + }); + } + + setOptions(options) { + // Normalize options to always be objects with value, label, and icon + this.allOptions = options.map((opt) => { + if (typeof opt === "string") { + return { value: opt, label: opt, icon: null }; + } + return { + value: opt.value || opt, + label: opt.label || opt.value || opt, + icon: opt.icon || null, + }; + }); + this.filteredOptions = [...this.allOptions]; + this.renderOptions(); + } + + setLoading(loading = true) { + if (loading) { + // Create loading element safely to prevent XSS + this.optionsContainer.innerHTML = ""; + const loadingDiv = document.createElement("div"); + loadingDiv.className = "slimstat-select-loading"; + loadingDiv.textContent = this.options.loadingText; + this.optionsContainer.appendChild(loadingDiv); + } + } + + filterOptions(searchTerm) { + const term = searchTerm.toLowerCase().trim(); + + if (!term) { + this.filteredOptions = [...this.allOptions]; + } else { + this.filteredOptions = this.allOptions.filter((option) => option.label.toLowerCase().includes(term) || option.value.toLowerCase().includes(term)); + } + + this.renderOptions(); + } + + renderOptions() { + this.optionsContainer.innerHTML = ""; + + if (this.filteredOptions.length === 0) { + // Create no results element safely to prevent XSS + const noResultsDiv = document.createElement("div"); + noResultsDiv.className = "slimstat-select-no-results"; + noResultsDiv.textContent = this.options.noResultsText; + this.optionsContainer.appendChild(noResultsDiv); + return; + } + + this.filteredOptions.forEach((option) => { + const optionElement = document.createElement("button"); + optionElement.type = "button"; + optionElement.className = "slimstat-select-option"; + if (option.value === this.selectedValue) { + optionElement.classList.add("slimstat-selected"); + } + + // Add icon if available + if (option.icon) { + const iconElement = document.createElement("img"); + iconElement.className = "slimstat-option-icon"; + iconElement.src = option.icon; + iconElement.alt = ""; + iconElement.width = 20; + iconElement.height = 20; + optionElement.appendChild(iconElement); + } + + // Add label text + const labelElement = document.createElement("span"); + labelElement.className = "slimstat-option-label"; + labelElement.textContent = option.label; + optionElement.appendChild(labelElement); + + optionElement.addEventListener("click", () => { + this.selectOption(option); + }); + this.optionsContainer.appendChild(optionElement); + }); + } + + selectOption(option) { + this.selectedValue = option.value; + this.selectedText = option.label; + this.selectedOption = option; + + // Update display + const textElement = this.display.querySelector(".slimstat-select-text"); + textElement.innerHTML = ""; // Clear existing content + + // Add icon if available + if (option.icon) { + const iconElement = document.createElement("img"); + iconElement.className = "slimstat-option-icon"; + iconElement.src = option.icon; + iconElement.alt = ""; + iconElement.width = 16; + iconElement.height = 16; + iconElement.style.marginRight = "6px"; + textElement.appendChild(iconElement); + } + + // Add label text + const labelSpan = document.createElement("span"); + labelSpan.textContent = option.label; + textElement.appendChild(labelSpan); + + this.display.classList.remove("slimstat-placeholder"); + + // Update hidden input with the value + this.element.value = option.value; + + // Ensure the name attribute is set + if (!this.element.hasAttribute("name")) { + this.element.setAttribute("name", "v"); + } + + // Trigger change event on original element + const changeEvent = new Event("change", { bubbles: true }); + this.element.dispatchEvent(changeEvent); + + this.close(); + } + + clear() { + this.selectedValue = ""; + this.selectedText = ""; + this.selectedOption = null; + + // Reset display + const textElement = this.display.querySelector(".slimstat-select-text"); + textElement.innerHTML = ""; // Clear any icons + textElement.textContent = this.options.placeholder; + this.display.classList.add("slimstat-placeholder"); + + // Clear hidden input + this.element.value = ""; + + // Trigger change event + const changeEvent = new Event("change", { bubbles: true }); + this.element.dispatchEvent(changeEvent); + } + + getValue() { + return this.selectedValue; + } + + setValue(value) { + const option = this.allOptions.find((opt) => opt.value === value); + if (option) { + this.selectOption(option); + } + } + + toggle() { + if (this.isOpen) { + this.close(); + } else { + this.open(); + } + } + + open() { + if (this.isOpen) return; + + this.isOpen = true; + this.selectWrapper.classList.add("slimstat-select-open"); + this.dropdown.style.display = "block"; + + // Focus search input + const searchInput = this.searchContainer.querySelector("input"); + searchInput.focus(); + searchInput.select(); + + // Reset filter + this.filterOptions(""); + } + + close() { + if (!this.isOpen) return; + + this.isOpen = false; + this.selectWrapper.classList.remove("slimstat-select-open"); + this.dropdown.style.display = "none"; + + // Clear search + const searchInput = this.searchContainer.querySelector("input"); + searchInput.value = ""; + } + + destroy() { + // Close dropdown if open + if (this.isOpen) { + this.close(); + } + + // Safely remove wrapper and restore original element + if (this.wrapper && this.element) { + // Move element back to its original position before wrapper + if (this.wrapper.parentNode) { + this.wrapper.parentNode.insertBefore(this.element, this.wrapper); + } + + // Show original element + this.element.style.display = ""; + + // Clear value + this.element.value = ""; + + // Remove wrapper + if (this.wrapper.parentNode) { + this.wrapper.parentNode.removeChild(this.wrapper); + } + } + } + } + + // Initialize searchable select instance + let searchableSelectInstance = null; + + /** + * Helper function to get current time range for AJAX requests + * Returns object with type, from, and to parameters + */ + function getTimeRangeForAjax() { + var urlParams = new URLSearchParams(window.location.search); + var timeRange = { + type: 'last_28_days', // default + from: '', + to: '' + }; + + // First, check URL parameters + if (urlParams.has('type')) { + var typeParam = urlParams.get('type'); + if (typeParam === 'custom' && urlParams.has('from') && urlParams.has('to')) { + timeRange.type = 'custom'; + timeRange.from = urlParams.get('from'); + timeRange.to = urlParams.get('to'); + } else if (typeParam !== 'custom') { + timeRange.type = typeParam; + } + } + // If no URL params, check sessionStorage + else { + var savedRange = sessionStorage.getItem('slimstat_date_range'); + if (savedRange) { + try { + var parsed = JSON.parse(savedRange); + if (parsed.preset) { + timeRange.type = parsed.preset; + } + // For custom ranges from sessionStorage + if (parsed.preset === 'custom' && parsed.startDate && parsed.endDate) { + timeRange.from = moment(parsed.startDate).format('YYYY-MM-DD'); + timeRange.to = moment(parsed.endDate).format('YYYY-MM-DD'); + } + } catch (e) { + // If parsing fails, use default + console.warn('SlimStat: Could not parse saved date range for filter options', e); + } + } + } + + return timeRange; + } + + // Handle dimension change to load filter options dynamically + jQuery("#slimstat-filter-name").on("change", function () { + var dimension = jQuery(this).val(); + + // Destroy existing searchable select FIRST before doing anything + if (searchableSelectInstance) { + searchableSelectInstance.destroy(); + searchableSelectInstance = null; + } + + // Get fresh reference to the input element after destroy + var $textInput = jQuery("#slimstat-filter-value"); + + if (!dimension) { + return; + } + + // Show loading state + $textInput.attr("placeholder", __("Loading options...", "wp-slimstat")).attr("name", "v"); + + // Get the current time range from URL parameters or sessionStorage + var timeRangeData = getTimeRangeForAjax(); + + // Fetch options via AJAX + jQuery.ajax({ + method: "POST", + url: ajaxurl, + data: { + action: "slimstat_get_filter_options", + dimension: dimension, + security: jQuery("#meta-box-order-nonce").val(), + time_range_type: timeRangeData.type, + time_range_from: timeRangeData.from, + time_range_to: timeRangeData.to, + }, + dataType: "json", + timeout: 30000, // 30 second timeout to prevent hanging requests + }) + .done(function (response) { + if (response.success) { + // Verify the element still exists + if (!$textInput.length || !$textInput[0]) { + return; + } + + try { + // Determine the appropriate "no results" message + var noResultsText = __('No matching options found', 'wp-slimstat'); + + // Check if we have no data due to time range filter + if (response.data && response.data.length === 0) { + noResultsText = __('No data in this time range', 'wp-slimstat'); + } + + // Initialize searchable select (even if no options) + searchableSelectInstance = new SlimStatSearchableSelect($textInput[0], { + placeholder: __('Select value...', 'wp-slimstat'), + searchPlaceholder: __('Search options...', 'wp-slimstat'), + noResultsText: noResultsText, + loadingText: __('Loading options...', 'wp-slimstat') + }); + + // Set the options from the AJAX response (empty array if no data) + searchableSelectInstance.setOptions(response.data || []); + + $textInput.attr("name", "v"); + } catch (error) { + // Fall back to regular text input if searchable select fails + console.error('SlimStat: Failed to initialize searchable select', error); + $textInput.attr("placeholder", __('Enter value...', 'wp-slimstat')).attr("name", "v"); + } + } else { + // On error response, fall back to text input + $textInput.attr("placeholder", __('Enter value...', 'wp-slimstat')).attr("name", "v"); + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + // On error, fall back to text input + $textInput.attr("placeholder", __("Enter value...", "wp-slimstat")).attr("name", "v"); + }); + }); + // Make input field read-only if certain operators are selected jQuery("#slimstat-filter-operator").on("change", function () { - if (this.value == "is_empty" || this.value == "is_not_empty") { - jQuery("#slimstat-filter-value").attr("readonly", "readonly"); + var operator = this.value; + var $textInput = jQuery("#slimstat-filter-value"); + + if (operator == "is_empty" || operator == "is_not_empty") { + $textInput.attr("readonly", "readonly"); + + // Disable searchable select if it exists + if (searchableSelectInstance) { + searchableSelectInstance.selectWrapper.style.pointerEvents = "none"; + searchableSelectInstance.selectWrapper.style.opacity = "0.5"; + } } else { - jQuery("#slimstat-filter-value").removeAttr("readonly"); + $textInput.removeAttr("readonly"); + + // Enable searchable select if it exists + if (searchableSelectInstance) { + searchableSelectInstance.selectWrapper.style.pointerEvents = "auto"; + searchableSelectInstance.selectWrapper.style.opacity = "1"; + } } }); @@ -364,6 +1150,38 @@ // ----- BEGIN: CUSTOMIZER ------------------------------------------------------- // + // Initialize sortable for customizer layout + if (jQuery(".meta-box-sortables").length) { + jQuery(".meta-box-sortables").sortable({ + connectWith: ".meta-box-sortables", + items: ".postbox", + placeholder: "sortable-placeholder", + handle: ".hndle", + cursor: "move", + delay: 150, + distance: 5, + tolerance: "pointer", + forcePlaceholderSize: true, + helper: "clone", + opacity: 0.65, + stop: function (event, ui) { + // Save the new order + var data = { + action: "meta-box-order", + _ajax_nonce: jQuery("#meta-box-order-nonce").val(), + page: SlimStatAdminParams.page_location + "_page_slimlayout", + page_columns: 0, + }; + + jQuery(".meta-box-sortables").each(function () { + data["order[" + this.id.split("-")[0] + "]"] = jQuery(this).sortable("toArray").join(","); + }); + + jQuery.post(ajaxurl, data); + }, + }); + } + // Clone and delete report placeholders jQuery(".slimstat-layout .slimstat-header-buttons a").on("click", function (e) { e.preventDefault(); @@ -397,6 +1215,53 @@ // ----- BEGIN: MISCELLANEOUS ---------------------------------------------------- // + + + function slimstatOpenHelp(fallbackUrl) { + var helpToggle = document.getElementById("contextual-help-link"); + if (helpToggle) { + var wasExpanded = helpToggle.getAttribute("aria-expanded") === "true"; + helpToggle.click(); + + if (!wasExpanded) { + window.setTimeout(function () { + var helpPanel = document.getElementById("contextual-help-wrap"); + if (helpPanel) { + if (!helpPanel.hasAttribute("tabindex")) { + helpPanel.setAttribute("tabindex", "-1"); + } + try { + helpPanel.focus({ preventScroll: true }); + } catch (err) { + helpPanel.focus(); + } + } + }, 50); + } + + return true; + } + + if (fallbackUrl) { + window.open(fallbackUrl, "_blank", "noopener"); + } + + return false; + } + + jQuery(document).on("click", "[data-slimstat-help-trigger]", function (e) { + e.preventDefault(); + slimstatOpenHelp(jQuery(this).data("slimstatHelpFallback")); + }); + + jQuery(document).on("keydown", "[data-slimstat-help-trigger]", function (e) { + var element = this; + slimstatHandleA11yActivation(e, function () { + slimstatOpenHelp(jQuery(element).data("slimstatHelpFallback")); + }); + }); + + // Hide a notice and send the corresponding ajax request to the server jQuery(document).on("click", "[id^=slimstat-notice-] button", function (e) { data = { @@ -594,31 +1459,96 @@ }, access_log_count_down: function () { - var slimstat_refresh_timer = 0; + var lastTriggerMinute = -1; + + function slimstat_sync_and_countdown() { + var now = new Date(); + var currentSeconds = now.getSeconds(); + var currentMinute = now.getMinutes(); + + // Trigger pulse at exactly :00 of a new minute + if (currentSeconds === 0 && lastTriggerMinute !== currentMinute) { + lastTriggerMinute = currentMinute; + window.dispatchEvent(new CustomEvent("slimstat:minute_pulse")); + } - function slimstat_refresh_countdown() { - slimstat_refresh_timer--; - minutes = parseInt(slimstat_refresh_timer / 60); - seconds = parseInt(slimstat_refresh_timer % 60); + var remaining = (60 - currentSeconds) % 60; + var minutes = Math.floor(remaining / 60); + var seconds = remaining % 60; jQuery(".refresh-timer").html(minutes + ":" + (seconds < 10 ? "0" : "") + seconds); + } - if (slimstat_refresh_timer == 0) { - // Request the data from the server - refresh = SlimStatAdmin.refresh_report("slim_p7_02"); + // Sync refresh with the global pulse + window.addEventListener("slimstat:minute_pulse", function () { + if (jQuery(".pagination .refresh-timer").length > 0) { + var refresh = SlimStatAdmin.refresh_report("slim_p7_02"); refresh(); + } + }); - // Reset the countdown timer - slimstat_refresh_timer = parseInt(SlimStatAdminParams.refresh_interval); + // Update online visitors count on pulse + window.addEventListener("slimstat:minute_pulse", function () { + var onlineVisitorsElement = document.getElementById("slimstat-online-visitors-count"); + var adminbarHeaderElement = document.getElementById("slimstat-adminbar-online-header"); + var adminbarCountElement = document.getElementById("slimstat-adminbar-online-count"); + + // Check if any element exists that needs updating + var hasElements = onlineVisitorsElement || adminbarHeaderElement || adminbarCountElement; + var securityNonce = jQuery("#meta-box-order-nonce").val(); + + if (hasElements && securityNonce) { + jQuery.ajax({ + url: ajaxurl, + type: "POST", + data: { + action: "slimstat_get_online_visitors", + security: securityNonce + }, + success: function (response) { + if (response.success && response.data && response.data.formatted) { + var newValue = response.data.count; + var formattedValue = response.data.formatted; + + // Helper function to animate value change + var animateElement = function(element) { + if (!element) return; + + var currentValue = element.textContent.replace(/,/g, ""); + if (parseInt(currentValue, 10) !== newValue) { + element.style.transition = "transform 0.1s ease-out"; + element.style.transform = "scale(1.05)"; + + setTimeout(function () { + element.textContent = formattedValue; + element.style.transform = "scale(1)"; + }, 100); + } + }; + + // Update all elements + animateElement(onlineVisitorsElement); + animateElement(adminbarHeaderElement); + animateElement(adminbarCountElement); + } + }, + error: function (xhr, status, error) { + console.error("Failed to update online visitors:", error); + } + }); } - } + }); var observer = new MutationObserver(function (mutationsList) { mutationsList.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { if (node.nodeType === 1 && node.classList.contains("refresh-timer")) { - slimstat_refresh_timer = parseInt(SlimStatAdminParams.refresh_interval); - SlimStatAdmin.refresh_handle = window.setInterval(slimstat_refresh_countdown, 1000); + if (SlimStatAdmin.refresh_handle != null) { + window.clearInterval(SlimStatAdmin.refresh_handle); + } + // Check every 200ms to ensure we catch the :00 second exactly + SlimStatAdmin.refresh_handle = window.setInterval(slimstat_sync_and_countdown, 200); + slimstat_sync_and_countdown(); } }); }); @@ -631,8 +1561,11 @@ }); if (jQuery(".pagination .refresh-timer").length > 0 && typeof SlimStatAdminParams.refresh_interval != "undefined") { - slimstat_refresh_timer = parseInt(SlimStatAdminParams.refresh_interval); - SlimStatAdmin.refresh_handle = window.setInterval(slimstat_refresh_countdown, 1000); + if (SlimStatAdmin.refresh_handle != null) { + window.clearInterval(SlimStatAdmin.refresh_handle); + } + SlimStatAdmin.refresh_handle = window.setInterval(slimstat_sync_and_countdown, 200); + slimstat_sync_and_countdown(); } }, get_query_string_filters: function (url) { Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/js: daterangepicker Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/js: live-analytics.js Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/js: migration.js Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/assets/js: notifications.js @@ -29,17 +29,18 @@ var args = JSON.parse(element.getAttribute("data-args")); var data = JSON.parse(element.getAttribute("data-data")); var prevData = JSON.parse(element.getAttribute("data-prev-data")); - var daysBetween = parseInt(element.getAttribute("data-days-between")); + var daysBetween = parseInt(element.getAttribute("data-days-between"), 10); var chartLabels = JSON.parse(element.getAttribute("data-chart-labels")); var translations = JSON.parse(element.getAttribute("data-translations")); var totals = JSON.parse(element.getAttribute("data-totals") || "{}"); + var chartType = element.getAttribute("data-chart-type") || "line"; var labels = data.labels; var prevLabels = data.prev_labels; // Fix: Check for null/undefined datasets before using them - var datasets = prepareDatasets(data.datasets, chartLabels, labels, data.today); - var prevDatasets = prepareDatasets(prevData.datasets, chartLabels, prevData.labels, null, true); + var datasets = prepareDatasets(data.datasets, chartLabels, labels, data.today, false, chartType); + var prevDatasets = prepareDatasets(prevData.datasets, chartLabels, prevData.labels, null, true, chartType); prevDatasets = prevDatasets.filter(function (ds) { return ( Array.isArray(ds.data) && @@ -57,7 +58,7 @@ chartCanvas.style.minHeight = "180px"; } var ctx = chartCanvas.getContext("2d"); - var chart = createChart(ctx, labels, prevLabels, datasets, prevDatasets, totals, args.granularity, data.today, translations, daysBetween, chartId); + var chart = createChart(ctx, labels, prevLabels, datasets, prevDatasets, totals, args.granularity, data.today, translations, daysBetween, chartId, chartType); charts[chartId] = chart; renderCustomLegend(chart, chartId, datasets, prevDatasets, totals, translations); } @@ -117,15 +118,16 @@ var translations2 = result.data.translations; var labels = data2.labels; - var datasets = prepareDatasets(data2.datasets, chart_labels2, labels, data2.today); - var prevDatasets = prepareDatasets(prev_data2.datasets, chart_labels2, prev_data2.labels, null, true); + var chartType2 = element.dataset.chartType || "line"; + var datasets = prepareDatasets(data2.datasets, chart_labels2, labels, data2.today, false, chartType2); + var prevDatasets = prepareDatasets(prev_data2.datasets, chart_labels2, prev_data2.labels, null, true, chartType2); // Destroy previous chart and create a new one to ensure correct tick callback var chartCanvas = document.getElementById("slimstat_chart_" + chartId); var prevChart = charts[chartId]; if (prevChart) prevChart.destroy(); var ctx = chartCanvas.getContext("2d"); - var chart = createChart(ctx, labels, data2.prev_labels, datasets, prevDatasets, totals2, granularity, data2.today, translations2, days_between2, chartId); + var chart = createChart(ctx, labels, data2.prev_labels, datasets, prevDatasets, totals2, granularity, data2.today, translations2, days_between2, chartId, chartType2); charts[chartId] = chart; renderCustomLegend(chart, chartId, datasets, prevDatasets, totals2, translations2); @@ -148,10 +150,13 @@ xhr.send(params); } - function prepareDatasets(rawDatasets, chartLabels, labels, today, isPrevious) { + function prepareDatasets(rawDatasets, chartLabels, labels, today, isPrevious, chartType) { if (typeof isPrevious === "undefined") { isPrevious = false; } + if (typeof chartType === "undefined") { + chartType = "line"; + } if (rawDatasets === undefined || rawDatasets === null) { return []; } @@ -191,23 +196,28 @@ (function (iCopy, labelTextCopy, valuesCopy, keyCopy) { var color = colors[iCopy % colors.length]; - result.push({ + var dataset = { label: isPrevious ? "Previous " + labelTextCopy : labelTextCopy, key: keyCopy, data: valuesCopy, borderColor: color, + backgroundColor: chartType === "bar" ? color + "40" : "transparent", borderWidth: isPrevious ? 1 : 2, - fill: false, - tension: 0.3, + fill: chartType === "bar" ? true : false, + tension: chartType === "line" ? 0.3 : 0, pointBorderColor: "transparent", pointBackgroundColor: color, pointBorderWidth: 2, - pointRadius: 0, + pointRadius: chartType === "bar" ? 0 : 0, pointHoverRadius: 4, pointHoverBorderWidth: 2, hitRadius: 10, pointHitRadius: 10, - segment: { + }; + + // Add segment configuration only for line charts + if (chartType === "line") { + dataset.segment = { borderDash: (function (isPrev) { return function (ctx) { if (isPrev) { @@ -216,15 +226,54 @@ return labels[ctx.p1DataIndex] === "'" + today + "'" ? [5, 3] : []; }; })(isPrevious), - }, - }); + }; + } + + // Add bar-specific properties + if (chartType === "bar") { + dataset.borderRadius = 6; + dataset.borderSkipped = false; + dataset.categoryPercentage = 0.8; + dataset.barPercentage = 0.9; + + // Add peak highlighting for bar charts + dataset.backgroundColor = function (context) { + var value = context.parsed.y; + var maxValue = Math.max.apply(Math, context.dataset.data); + if (value === maxValue && value > 0) { + return color + "CC"; + } + return color + "40"; + }; + } + + result.push(dataset); })(i, labelText, values, key); i++; } return result; } - function createChart(ctx, labels, prevLabels, datasets, prevDatasets, total, unitTime, today, translations, daysBetween, chartId) { + function isAllZeroDatasets(datasets) { + if (!Array.isArray(datasets) || datasets.length === 0) { + return true; + } + for (var i = 0; i < datasets.length; i++) { + var ds = datasets[i]; + if (!ds || !Array.isArray(ds.data)) { + continue; + } + for (var j = 0; j < ds.data.length; j++) { + var v = ds.data[j]; + if (typeof v === "number" && v > 0) { + return false; + } + } + } + return true; + } + + function createChart(ctx, labels, prevLabels, datasets, prevDatasets, total, unitTime, today, translations, daysBetween, chartId, chartType) { var isRTL = document.documentElement.dir === "rtl" || document.body.classList.contains("rtl"); var customCrosshair = { @@ -254,6 +303,25 @@ ctx2.restore(); }, }; + var emptyLine = { + id: "emptyLine", + afterDraw: function (chart) { + var opts = chart.options && chart.options.plugins && chart.options.plugins.emptyLine; + if (!opts || !opts.enabled) return; + var area = chart.chartArea; + if (!area) return; + var ctx2 = chart.ctx; + var y = (area.top + area.bottom) / 2; + ctx2.save(); + ctx2.strokeStyle = opts.color || "#e8294c"; + ctx2.lineWidth = 2; + ctx2.beginPath(); + ctx2.moveTo(area.left, y); + ctx2.lineTo(area.right, y); + ctx2.stroke(); + ctx2.restore(); + }, + }; var maxTicks = 8; var uniqueTickIndexes = []; @@ -339,17 +407,85 @@ return slimstatGetLabel(label, false, unitTime, translations); } catch (e) { console.warn("SlimStat: Error processing label:", label, e); - return label; // Return original label if processing fails + return label; } } return ""; } + // Prepare datasets with chart type + var preparedDatasets = datasets.concat(prevDatasets); + var isEmptyCurrent = isAllZeroDatasets(datasets); + + if (isEmptyCurrent && labels.length === 0) { + labels = [""]; + for (var ed = 0; ed < preparedDatasets.length; ed++) { + if (!Array.isArray(preparedDatasets[ed].data)) { + preparedDatasets[ed].data = [0]; + } else if (preparedDatasets[ed].data.length === 0) { + preparedDatasets[ed].data = [0]; + } + } + } + + if (isEmptyCurrent) { + preparedDatasets.unshift({ + label: "", + key: "__empty__", + skipLegend: true, + type: "line", + data: labels.map(function () { + return 0; + }), + borderColor: "#e8294c", + backgroundColor: "transparent", + borderWidth: 2, + fill: false, + tension: 0, + pointRadius: 0, + pointHoverRadius: 0, + pointHitRadius: 0, + hitRadius: 0, + }); + } + + for (var d = 0; d < preparedDatasets.length; d++) { + var ds = preparedDatasets[d]; + if (chartType === "bar") { + // Only set backgroundColor if it's not already a function (for peak highlighting) + if (typeof ds.backgroundColor !== "function") { + ds.backgroundColor = ds.backgroundColor || ds.borderColor + "40"; + } + ds.borderRadius = 6; + ds.borderSkipped = false; + ds.categoryPercentage = 0.8; + ds.barPercentage = 0.9; + } + } + + var yScale = { + ticks: { + font: { + family: "Open Sans, sans-serif", + }, + color: "#222", + }, + grid: { + display: false, + }, + }; + + if (isEmptyCurrent) { + yScale.min = -1; + yScale.max = 1; + yScale.ticks.stepSize = 1; + } + return new Chart(ctx, { - type: "line", + type: chartType || "line", data: { labels: labels, - datasets: datasets.concat(prevDatasets), + datasets: preparedDatasets, }, options: { layout: { padding: 20 }, @@ -388,6 +524,10 @@ titleColor: "#222", bodyColor: "#222", }, + emptyLine: { + enabled: isEmptyCurrent, + color: "#e8294c", + }, }, scales: { x: { @@ -409,15 +549,10 @@ }, }, y: { - ticks: { - font: { - family: "Open Sans, sans-serif", - }, - color: "#222", - }, - grid: { - display: false, - }, + ticks: yScale.ticks, + grid: yScale.grid, + min: yScale.min, + max: yScale.max, }, }, maintainAspectRatio: false, @@ -433,7 +568,7 @@ mode: "index", }, }, - plugins: [customCrosshair], + plugins: [customCrosshair, emptyLine], }); } @@ -443,6 +578,9 @@ for (var di = 0; di < chart.data.datasets.length; di++) { (function (index) { var dataset = chart.data.datasets[index]; + if (dataset.skipLegend) { + return; + } var isPrevious = dataset.label.indexOf("Previous") !== -1; if (isPrevious) { return; @@ -459,7 +597,7 @@ html += '<span class="slimstat-postbox-chart--item-label">' + dataset.label + "</span>"; html += '<span class="slimstat-postbox-chart--item--color" style="background-color: ' + dataset.borderColor + '"></span>'; html += '<span class="slimstat-postbox-chart--item-value">' + currentValue.toLocaleString() + "</span>"; - if (previousValue && previousValue !== currentValue) { + if (totals.previous && totals.previous[key] != null) { html += '<span class="slimstat-postbox-chart--item--color" style="background-image: repeating-linear-gradient(to right, ' + dataset.borderColor + ", " + dataset.borderColor + ' 4px, transparent 0px, transparent 6px); background-size: auto 6px; height: 2px; margin-bottom: 0px; margin-left: 10px;"></span>'; html += '<span class="slimstat-postbox-chart--item-value">' + previousValue.toLocaleString() + "</span>"; } @@ -627,7 +765,7 @@ } } catch (e) { console.warn("SlimStat: Error processing monthly label:", label, e); - return label; // Return original label if processing fails + return label; } } // Debug: Log labels that don't match the expected format @@ -762,20 +900,7 @@ innerHtml += "</td></tr>"; } innerHtml += "</tbody>"; - innerHtml += - '<div class="align-indicator" style="\ - width: 15px;\ - height: 15px;\ - background-color: #fff;\ - border-bottom-left-radius: 5px;\ - display: inline-block;\ - position: absolute;\ - bottom: -8px;\ - border-bottom: solid 1px #e0e0e0;\ - border-left: solid 1px #e0e0e0;\ - transform: rotate(-45deg);\ - transition: left 0.1s ease;\ - "></div>'; + innerHtml += '<div class="align-indicator" style="' + "width: 15px;" + "height: 15px;" + "background-color: #fff;" + "border-bottom-left-radius: 5px;" + "display: inline-block;" + "position: absolute;" + "bottom: -8px;" + "border-bottom: solid 1px #e0e0e0;" + "border-left: solid 1px #e0e0e0;" + "transform: rotate(-45deg);" + "transition: left 0.1s ease;" + '"></div>'; tooltipEl.querySelector("table").innerHTML = innerHtml; @@ -26,7 +26,8 @@ &.slimstat_page_slimview5, &.slimstat_page_slimconfig, &.slimstat_page_slimpro, - &.slimstat_page_slimlayout { + &.slimstat_page_slimlayout, + &.slimstat_page_migration { background: #f5f6fa !important; } } @@ -60,6 +61,7 @@ } .slimstat-browser-icon, + .slimstat-flag-icon, span.slimstat-flag-container { position: relative !important; width: 18px; @@ -142,10 +144,19 @@ justify-content: center; // align-items: center; + [dir="rtl"] & { + margin-left: 0; + margin-right: 0px; + } + @media (max-width: 768px) { margin-left: 0; min-width: 100%; max-width: 100%; + + [dir="rtl"] & { + margin-right: 0; + } } } @@ -227,6 +238,11 @@ .top-countries { margin-left: 0; margin-top: 20px; + + [dir="rtl"] & { + margin-left: 0; + margin-right: 0; + } } } @@ -293,10 +309,19 @@ justify-content: center; // align-items: center; + [dir="rtl"] & { + margin-left: 0; + margin-right: 30px; + } + @media (max-width: 768px) { margin-left: 0; min-width: 100%; max-width: 100%; + + [dir="rtl"] & { + margin-right: 0; + } } } @@ -379,6 +404,11 @@ .top-countries { margin-left: 0; margin-top: 20px; + + [dir="rtl"] & { + margin-left: 0; + margin-right: 0; + } } } } @@ -392,7 +422,8 @@ &.slimstat_page_slimview5, &.slimstat_page_slimconfig, &.slimstat_page_slimpro, - &.slimstat_page_slimlayout { + &.slimstat_page_slimlayout, + &.slimstat_page_migration { .jqvmap-label { z-index: 999999; position: absolute !important; @@ -455,6 +486,11 @@ .export-pro-badge { margin-left: 3px; margin-bottom: -5px; + + [dir="rtl"] & { + margin-left: 0; + margin-right: 3px; + } } // Old Styles @@ -496,7 +532,7 @@ right: 0; z-index: 1; - .rtl & { + [dir="rtl"] & { left: 0; right: auto; } @@ -539,7 +575,7 @@ z-index: 120; border-radius: 8px; - .rtl & { + [dir="rtl"] & { right: auto; left: -4px; } @@ -569,7 +605,7 @@ float: right; margin: 2px 5px 0 0; - .rtl & { + [dir="rtl"] & { margin: 2px 0 0 5px; } } @@ -626,7 +662,7 @@ margin-right: -0.5%; } - .rtl & { + [dir="rtl"] & { margin-right: 0; margin-left: -1.3%; @@ -868,14 +904,15 @@ &.slimstat_page_slimview4, &.slimstat_page_slimview5, &.slimstat_page_slimconfig, + &.slimstat_page_migration, &.slimstat_page_slimpro, &.slimstat_page_slimlayout { /* Hide all non-Slimstat notices in the admin */ - div[class*="-notice"]:not(.slimstat-notice), - div[class*="admin-notice"]:not(.slimstat-notice), - .notice:not(.slimstat-notice), - .update-nag:not(.slimstat-notice), - .error:not(.slimstat-notice) { + div[class*="-notice"]:not(.slimstat-notice):not(.slimstat-migration-notice), + div[class*="admin-notice"]:not(.slimstat-notice):not(.slimstat-migration-notice), + .notice:not(.slimstat-notice):not(.slimstat-migration-notice), + .update-nag:not(.slimstat-notice):not(.slimstat-migration-notice), + .error:not(.slimstat-notice):not(.slimstat-migration-notice) { display: none !important; } } @@ -984,6 +1021,15 @@ .form-table { border: 1px solid #ccc; margin-top: 0; + overflow: hidden; + + *:not(.bootstrap-switch-container):not(input):not(button):not(label):not(select):not(textarea):not(a):not(code):not(pre):not(.description) { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-wrap: auto; + } } .form-table th { @@ -1040,6 +1086,11 @@ .wp-list-table.slimstat-addons tbody th { border-left: 5px solid #ccc; + + [dir="rtl"] & { + border-left: 0; + border-right: 5px solid #ccc; + } } .wp-list-table.slimstat-addons th, @@ -1051,6 +1102,10 @@ border-color: #10a062; border-style: solid; border-radius: 0 0 0 5px; + + [dir="rtl"] & { + border-radius: 0 0 5px 0; + } } .wp-list-table.slimstat-addons .active td { @@ -1118,6 +1173,262 @@ padding: 0 0 0 30px; } +/* Additional RTL Support for Dashboard Elements */ +.rtl .slimstat-browser-icon, +.rtl .slimstat-flag-icon, +.rtl span.slimstat-flag-container { + float: right !important; + margin: 0px 0px 0px 10px !important; +} + +.rtl .slimstat-author-link img { + float: right !important; + margin: 0px 0px 0px 10px !important; +} + +.rtl .slimstat-float-right { + float: left; +} + +.rtl #slimstat-date-filters .dropdown .ui-datepicker-trigger { + float: left; + margin: 2px 0 0 5px; +} + +.rtl #slimstat-current-filters .slimstat-filter-list { + float: right; + padding: 4px 5px 5px 0; +} + +.rtl .slimstat-filter-action-button { + float: left; + margin-right: 10px !important; + margin-left: 0 !important; +} + +.rtl .wrap.slimstat .sortable-placeholder { + float: right; +} + +.rtl [id^="slim_"] p.pagination a { + float: left; + margin-right: 5px; + margin-left: 0; +} + +.rtl [id^="slim_"] p span { + float: left; +} + +.rtl [id^="slim_"] p span.pageview-screenres { + margin-right: 10px; + margin-left: 0; +} + +.rtl [id^="slim_"] .inline-icon { + margin-left: 5px; + margin-right: 0; +} + +.rtl [id^="slim_"] .spaced { + margin-right: 15px; + margin-left: 0; +} + +.rtl .little-color-box { + float: right; + margin-left: 10px; + margin-right: 0; +} + +.rtl .ui-dialog.slimstat .ui-dialog-titlebar-close { + float: left; +} + +.rtl [class*="bootstrap-switch-id-addon_network_settings"] { + float: left; +} + +.rtl .slimstat-layout .postbox, +.rtl .slimstat-layout .sortable-placeholder { + float: right; + margin: 0 0 10px 10px; +} + +.rtl .ui-datepicker .ui-datepicker-buttonpane button { + float: left; +} + +.rtl .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { + float: right; +} + +.rtl .ui-datepicker-multi .ui-datepicker-group { + float: right; +} + +.rtl .tag-editor li { + float: right; +} + +.rtl .tag-editor div { + float: right; +} + +.rtl .tag-editor .tag-editor-tag { + padding-right: 5px; + padding-left: 0; + border-radius: 2px 0 0 2px; +} + +.rtl .tag-editor .tag-editor-delete { + padding-right: 4px; + padding-left: 3px; + border-radius: 0 2px 2px 0; +} + +.rtl .ui-autocomplete { + right: 0; + left: auto; +} + +.rtl .backdrop-container { + @media (min-width: 780px) { + margin-right: -20px; + margin-left: 0; + padding-right: 20px; + padding-left: 0; + } +} + +.rtl .slimstat-filter-link .avatar { + margin-left: 2px; + margin-right: 0; +} + +.rtl #slimstat-date-filters a.slimstat-filter-link { + float: right; + margin: 0 0 5px 5px; +} + +.rtl .slimstat-header-buttons { + float: left; +} + +.rtl .slimstat-header-buttons a.slimstat-font-download span.dashicons::before { + margin-left: 2px; + margin-right: 0; +} + +.rtl .slimstat-browser-icon, +.rtl .slimstat-flag-icon, +.rtl span.slimstat-flag-container { + float: right !important; + margin: 0px 0px 0px 10px !important; +} + +.rtl .slimstat-author-link img { + float: right !important; + margin: 0px 0px 0px 10px !important; +} + +.rtl .slimstat-tooltip-trigger .slimstat-tooltip-bar-wrap { + right: 0; + left: auto; + margin: 0px 15px 0px 0px; +} + +.rtl .pagination { + right: 0; + left: auto; +} + +.rtl .slimstat-header { + margin-right: -20px; + margin-left: 0; +} + +.rtl .slimstat-header .go-pro { + text-align: right; +} + +.rtl .slimstat-header .go-pro p { + text-align: right; +} + +.rtl .slimstat-header .pro-badge { + right: auto; + left: 20px; +} + +.rtl .slimstat-header .pro-badge .icon { + margin-left: 7px; + margin-right: 0; +} + +.rtl .slimstat-header .go-pro a .icon { + margin-left: 0; + margin-right: 5px; + transform: scaleX(-1); +} + +.rtl .slimstat-pro-modal #slimstat-pro-modal-close { + left: 10%; + right: auto; +} + +.rtl .slimstat-pro-modal .features-flex-box .feature-item h6 .icon { + margin-left: 7px; + margin-right: 0; +} + +.rtl .slimstat-pro-modal .features-flex-box .feature-item .more-info-icon { + margin-right: 3px; + margin-left: 0; +} + +/* RTL Support for responsive elements */ +@media screen and (max-width: 800px) { + .rtl #slimstat-date-filters { + left: inherit; + right: inherit; + } +} + +@media screen and (max-width: 600px) { + .rtl [id^="slim_"] .spaced { + margin-right: 5px; + margin-left: 5px; + } + + .rtl [id^="slim_"] .inline-icon { + margin-left: 2px; + margin-right: 0; + } + + .rtl [id^="slim_"] p span.pageview-screenres { + margin-right: 3px; + margin-left: 0; + } +} + +@media screen and (max-width: 400px) { + .rtl [id^="slim_"] .spaced { + margin-right: 8px; + margin-left: 8px; + } + + .rtl [id^="slim_"] .inline-icon { + margin-left: 3px; + margin-right: 0; + } + + .rtl [id^="slim_"] p span.pageview-screenres { + margin-right: 5px; + margin-left: 0; + } +} + /* Customizer */ .slimstat-layout .postbox-container { float: none; @@ -1147,6 +1458,19 @@ min-width: 285px; } +.slimstat-layout .sortable-placeholder { + border: 2px dashed #72aee6; + background-color: #f0f6fc; + visibility: visible !important; + height: 80px; + border-radius: 5px; +} + +.slimstat-layout .postbox.ui-sortable-helper { + opacity: 0.65; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); +} + .slimstat-layout h3 { border: 0; font-weight: 300; @@ -1731,12 +2055,20 @@ padding: 5px 9px; text-align: left; word-wrap: break-word; + + [dir="rtl"] & { + text-align: right; + } } .qtip-titlebar { padding: 5px 35px 5px 10px; border-radius: 0 0 1px; font-weight: 700; + + [dir="rtl"] & { + padding: 5px 10px 5px 35px; + } } .qtip-titlebar + .qtip-content { @@ -1751,12 +2083,22 @@ cursor: pointer; outline: 0; border: 1px solid transparent; + + [dir="rtl"] & { + right: auto; + left: -9px; + } } .qtip-titlebar .qtip-close { right: 4px; top: 50%; margin-top: -9px; + + [dir="rtl"] & { + right: auto; + left: 4px; + } } * html .qtip-titlebar .qtip-close { @@ -1806,6 +2148,10 @@ text-decoration: none !important; transform: translateX(9px) translateY(-1px); + [dir="rtl"] & { + transform: translateX(-9px) translateY(-1px); + } + strong { color: #fff; font-weight: 700; @@ -1857,12 +2203,22 @@ line-height: unset !important; border-top-right-radius: 4px; display: inline-block; + + [dir="rtl"] & { + border-top-right-radius: 0; + border-top-left-radius: 4px; + } } .qtip .qtip-tip canvas { top: 0; left: 0; display: none !important; + + [dir="rtl"] & { + left: auto; + right: 0; + } } .qtip .qtip-tip .qtip-vml { @@ -1922,10 +2278,20 @@ .ui-datepicker .ui-datepicker-prev { left: 2px; + + [dir="rtl"] & { + left: auto; + right: 2px; + } } .ui-datepicker .ui-datepicker-next { right: 2px; + + [dir="rtl"] & { + right: auto; + left: 2px; + } } .ui-datepicker .ui-datepicker-title { @@ -1973,6 +2339,10 @@ padding: 0.2em; text-align: right; text-decoration: none; + + [dir="rtl"] & { + text-align: left; + } } .ui-datepicker .ui-datepicker-buttonpane { @@ -1982,6 +2352,11 @@ border-left: 0; border-right: 0; border-bottom: 0; + + [dir="rtl"] & { + border-left: 0; + border-right: 0; + } } .ui-datepicker .ui-datepicker-buttonpane button { @@ -1991,10 +2366,18 @@ padding: 0.2em 0.6em 0.3em; width: auto; overflow: visible; + + [dir="rtl"] & { + float: left; + } } .ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float: left; + + [dir="rtl"] & { + float: right; + } } .ui-datepicker.ui-datepicker-multi { @@ -2003,6 +2386,10 @@ .ui-datepicker-multi .ui-datepicker-group { float: left; + + [dir="rtl"] & { + float: right; + } } .ui-datepicker-multi .ui-datepicker-group table { @@ -2025,10 +2412,19 @@ .ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, .ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width: 0; + + [dir="rtl"] & { + border-left-width: 1px; + border-right-width: 0; + } } .ui-datepicker-multi .ui-datepicker-buttonpane { clear: left; + + [dir="rtl"] & { + clear: right; + } } .ui-datepicker-row-break { @@ -2063,21 +2459,38 @@ .ui-datepicker-rtl .ui-datepicker-buttonpane { clear: right; + + [dir="rtl"] & { + clear: left; + } } .ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; + + [dir="rtl"] & { + float: right; + } } .ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, .ui-datepicker-rtl .ui-datepicker-group { float: right; + + [dir="rtl"] & { + float: left; + } } .ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, .ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width: 0; border-left-width: 1px; + + [dir="rtl"] & { + border-right-width: 1px; + border-left-width: 0; + } } /*! bootstrap-switch - v3.3.4 | https://www.bootstrap-switch.org */ @@ -2100,6 +2513,11 @@ -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; + + [dir="rtl"] & { + direction: rtl; + text-align: right; + } } .bootstrap-switch .bootstrap-switch-container { @@ -2178,11 +2596,25 @@ .bootstrap-switch .bootstrap-switch-handle-on { border-bottom-left-radius: 3px; border-top-left-radius: 3px; + + [dir="rtl"] & { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; + } } .bootstrap-switch .bootstrap-switch-handle-off { border-bottom-right-radius: 3px; border-top-right-radius: 3px; + + [dir="rtl"] & { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + } } .bootstrap-switch input[type="radio"], @@ -2194,6 +2626,11 @@ z-index: -1; opacity: 0 !important; filter: alpha(opacity=0); + + [dir="rtl"] & { + left: auto; + right: 0; + } } .bootstrap-switch.bootstrap-switch-mini .bootstrap-switch-handle-off, @@ -2244,26 +2681,54 @@ -webkit-transition: margin-left 0.5s; -o-transition: margin-left 0.5s; transition: margin-left 0.5s; + + [dir="rtl"] & { + -webkit-transition: margin-right 0.5s; + -o-transition: margin-right 0.5s; + transition: margin-right 0.5s; + } } .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-on { border-radius: 0 3px 3px 0; + + [dir="rtl"] & { + border-radius: 3px 0 0 3px; + } } .bootstrap-switch.bootstrap-switch-inverse .bootstrap-switch-handle-off { border-radius: 3px 0 0 3px; + + [dir="rtl"] & { + border-radius: 0 3px 3px 0; + } } .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-off .bootstrap-switch-label, .bootstrap-switch.bootstrap-switch-on .bootstrap-switch-label { border-bottom-right-radius: 3px; border-top-right-radius: 3px; + + [dir="rtl"] & { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + } } .bootstrap-switch.bootstrap-switch-inverse.bootstrap-switch-on .bootstrap-switch-label, .bootstrap-switch.bootstrap-switch-off .bootstrap-switch-label { border-bottom-left-radius: 3px; border-top-left-radius: 3px; + + [dir="rtl"] & { + border-bottom-left-radius: 0; + border-top-left-radius: 0; + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; + } } /* jQuery tagEditor v1.0.21 | https://github.com/Pixabay/jQuery-tagEditor */ @@ -2278,6 +2743,10 @@ color: #555; background: #fff; line-height: 20px; + + [dir="rtl"] & { + padding: 0 0 0 5px; + } } .tag-editor li { @@ -2285,11 +2754,19 @@ float: left; overflow: hidden; margin: 3px 0; + + [dir="rtl"] & { + float: right; + } } .tag-editor div { float: left; padding: 0 4px; + + [dir="rtl"] & { + float: right; + } } .tag-editor .placeholder { @@ -2303,6 +2780,10 @@ overflow: hidden; color: transparent; background: none; + + [dir="rtl"] & { + width: 8px; + } } .tag-editor input { @@ -2323,7 +2804,21 @@ .tag-editor-hidden-src { position: absolute !important; - left: -99999px; + left: 0; + top: 0; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + padding: 0; + margin: 0; + border: 0; + + [dir="rtl"] & { + left: auto; + right: 0; + } } .tag-editor ::-ms-clear { @@ -2338,6 +2833,12 @@ overflow: hidden; cursor: pointer; border-radius: 2px 0 0 2px; + + [dir="rtl"] & { + padding-left: 0; + padding-right: 5px; + border-radius: 0 2px 2px 0; + } } .tag-editor .tag-editor-delete { @@ -2346,6 +2847,12 @@ border-radius: 0 2px 2px 0; padding-left: 3px; padding-right: 4px; + + [dir="rtl"] & { + padding-left: 4px; + padding-right: 3px; + border-radius: 2px 0 0 2px; + } } .tag-editor .tag-editor-delete i { @@ -2380,6 +2887,11 @@ left: 0; cursor: default; font-size: 14px; + + [dir="rtl"] & { + left: auto; + right: 0; + } } .ui-front { @@ -2433,6 +2945,13 @@ margin-top: -20px; padding-left: 20px; padding-top: 20px; + + [dir="rtl"] & { + margin-left: 0; + margin-right: -20px; + padding-left: 0; + padding-right: 20px; + } } } @@ -2443,6 +2962,11 @@ transform: translateY(3px); margin-right: 2px; border: solid 1px #fff; + + [dir="rtl"] & { + margin-right: 0; + margin-left: 2px; + } } // New Styles @@ -2496,6 +3020,11 @@ position: fixed; z-index: 2; display: none; + + [dir="rtl"] & { + left: auto; + right: 0; + } } #slimstat-date-filters { @@ -2511,6 +3040,11 @@ color: $grayOne; transition: 0.3s all ease-out; + [dir="rtl"] & { + float: right; + margin: 0 0 5px 5px; + } + &:hover { background-color: $grayFour; } @@ -2544,6 +3078,11 @@ padding-left: 12px; padding-right: 12px; + [dir="rtl"] & { + padding-left: 12px; + padding-right: 12px; + } + &:hover { background-color: black; } @@ -2601,6 +3140,12 @@ border: none; margin-top: 25px; border-radius: 10px; + /* Ensure table never exceeds container width */ + width: 100%; + max-width: 100%; + /* Prefer content wrapping over horizontal scroll */ + border-collapse: separate; + table-layout: auto; tr { &:first-of-type { @@ -2618,25 +3163,39 @@ th { padding: 25px 20px; color: $grayOne; + /* Allow header text to wrap and not force width */ + min-width: 0; + white-space: normal; + word-break: break-word; + overflow-wrap: anywhere; } td { padding: 25px 20px; color: $grayOne; + /* Let cell content wrap and avoid overflow */ + min-width: 0; + white-space: normal; + word-break: break-word; + overflow-wrap: anywhere; input { border: 2px solid $grayFour; border-radius: 50px; + /* Avoid input overflow in narrow viewports */ + max-width: 100%; } textarea { border: 2px solid $grayFour; border-radius: 10px; + max-width: 100%; } select { border: 2px solid $grayFour; border-radius: 50px; + max-width: 100%; } .button-primary { @@ -2644,6 +3203,18 @@ color: $brand; background-color: white; border-radius: 50px; + max-width: 100%; + } + + /* Make long links and code wrap instead of pushing layout */ + a, + code, + pre, + .description { + white-space: normal; + word-break: break-word; + overflow-wrap: anywhere; + max-width: 100%; } .tag-editor { @@ -2689,6 +3260,8 @@ color: $grayTwo; margin-top: 7px; display: block; + word-wrap: break-word; + max-width: 100%; } } } @@ -2718,6 +3291,13 @@ display: block; width: 100%; margin: 0; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolyline points='4 6 8 10 12 6' fill='none' stroke='%23666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 8px center; + padding-right: 32px; } input { @@ -2734,8 +3314,9 @@ color: $brand; width: auto; - .rtl & { + [dir="rtl"] & { margin-left: 0; + margin-right: 0; } } } @@ -2763,7 +3344,7 @@ // box-shadow: 3px 0px 15px 0px rgba(0, 0, 0, 0.02); margin-right: 0.5%; - .rtl & { + [dir="rtl"] & { margin-right: 0; margin-left: 0.5%; } @@ -2849,7 +3430,7 @@ } } - .rtl & { + [dir="rtl"] & { float: left; a.slimstat-font-download span.dashicons::before { @@ -2880,6 +3461,7 @@ } .slimstat-browser-icon, + .slimstat-flag-icon, span.slimstat-flag-container { position: relative !important; width: 18px; @@ -3044,6 +3626,12 @@ margin-left: -20px; box-sizing: border-box; + [dir="rtl"] & { + margin-left: 0; + margin-right: -20px; + justify-content: flex-end; + } + .logo { width: 180px; height: auto; @@ -3060,6 +3648,10 @@ .go-pro { text-align: left; + [dir="rtl"] & { + text-align: right; + } + a { display: flex; align-items: center; @@ -3071,12 +3663,22 @@ font-weight: 400; cursor: pointer; + [dir="rtl"] & { + justify-content: flex-end; + } + .icon { background: url("../images/white-right-chevron.png") no-repeat center center/contain; width: 10px; height: 10px; margin-left: 5px; display: block; + + [dir="rtl"] & { + margin-left: 0; + margin-right: 5px; + transform: scaleX(-1); + } } } @@ -3085,6 +3687,10 @@ font-weight: 300; color: white; margin: 0; + + [dir="rtl"] & { + text-align: right; + } } } @@ -3097,6 +3703,12 @@ bottom: 20px; color: white; + [dir="rtl"] & { + right: auto; + left: 20px; + justify-content: flex-end; + } + p { margin: 0; } @@ -3107,10 +3719,15 @@ height: 16px; margin-right: 7px; display: block; + + [dir="rtl"] & { + margin-right: 0; + margin-left: 7px; + } } } - .rtl & { + [dir="rtl"] & { margin-right: -20px; margin-left: 0; @@ -3154,7 +3771,7 @@ .pro-badge { right: 10px; - .rtl & { + [dir="rtl"] & { left: 10px; right: auto; } @@ -3242,6 +3859,10 @@ top: 45%; transform: translateX(-50%) translateY(-50%); + [dir="rtl"] & { + transform: translateX(50%) translateY(-50%); + } + * { margin: 0; padding: 0; @@ -3259,6 +3880,11 @@ transition: 0.3s all ease-out; background: url("data:image/svg+xml,%3Csvg width='33' height='32' viewBox='0 0 33 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.6172 31.1641C24.9015 31.1641 31.6172 24.4483 31.6172 16.1641C31.6172 7.87979 24.9015 1.16406 16.6172 1.16406C8.33292 1.16406 1.61719 7.87979 1.61719 16.1641C1.61719 24.4483 8.33292 31.1641 16.6172 31.1641Z' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M21.1172 11.6641L12.1172 20.6641' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12.1172 11.6641L21.1172 20.6641' stroke='%23222222' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center / contain; + [dir="rtl"] & { + right: auto; + left: 10%; + } + &:hover { opacity: 1; } @@ -3346,6 +3972,11 @@ margin-right: 7px; display: inline-block; background: url("data:image/svg+xml,%3Csvg width='18' height='17' viewBox='0 0 18 17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M16.4463 7.90039V8.57696C16.4454 10.1628 15.9319 11.7058 14.9823 12.976C14.0328 14.2461 12.6982 15.1753 11.1774 15.625C9.65665 16.0746 8.03129 16.0206 6.54373 15.471C5.05617 14.9215 3.78612 13.9057 2.92298 12.5754C2.05985 11.245 1.64988 9.67128 1.75422 8.08889C1.85857 6.50649 2.47163 5.00021 3.50198 3.79471C4.53233 2.5892 5.92476 1.74905 7.4716 1.39956C9.01844 1.05007 10.6368 1.20997 12.0854 1.8554' stroke='%23E8294C' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M16.445 2.69531L9.09097 10.0567L6.88477 7.85047' stroke='%23E8294C' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center / contain; + + [dir="rtl"] & { + margin-right: 0; + margin-left: 7px; + } } } @@ -3355,6 +3986,11 @@ height: 20px; margin-left: 3px; background: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.20996 15.4014C12.1434 15.4014 15.332 12.2127 15.332 8.2793C15.332 4.34589 12.1434 1.15723 8.20996 1.15723C4.27655 1.15723 1.08789 4.34589 1.08789 8.2793C1.08789 12.2127 4.27655 15.4014 8.20996 15.4014Z' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M6.13672 6.14245C6.30416 5.66645 6.63466 5.26508 7.06968 5.00942C7.5047 4.75376 8.01616 4.6603 8.51348 4.7456C9.0108 4.83091 9.46188 5.08946 9.78683 5.47548C10.1118 5.8615 10.2896 6.35007 10.2889 6.85465C10.2889 8.27907 8.15226 8.99127 8.15226 8.99127' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M8.21094 11.8398H8.21769' stroke='%23C3C4C7' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A") no-repeat center center / contain; + + [dir="rtl"] & { + margin-left: 0; + margin-right: 3px; + } } } } @@ -3378,7 +4014,7 @@ } } - .rtl & { + [dir="rtl"] & { .features-flex-box { .feature-item { h6 .icon { @@ -3402,6 +4038,11 @@ #slimstat-pro-modal-close { top: 20px; right: 20px; + + [dir="rtl"] & { + right: auto; + left: 20px; + } } h2 { @@ -3421,9 +4062,209 @@ h6 { .icon { transform: translateY(4px); + + [dir="rtl"] & { + margin-right: 0; + margin-left: 7px; + } } } } } } } + +/* Custom Searchable Select Styles */ +.slimstat-searchable-select { + position: relative; + display: inline-block; + width: 100%; + + .slimstat-select-wrapper { + width: 180px; + margin-right: 5px; + position: relative; + border: 1px solid $graySix; + border-radius: 5px; + background: #fff; + cursor: pointer; + min-height: 32px; + + &:hover { + border-color: $brand; + } + + &.slimstat-select-open { + border-color: $brand; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + } + + .slimstat-select-display { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 12px; + min-height: 20px; + font-size: 14px; + color: #555; + + &.slimstat-placeholder { + color: #999; + } + + .slimstat-select-text { + flex: 1; + text-align: left; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .slimstat-select-arrow { + flex-shrink: 0; + margin-left: 8px; + transition: transform 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: #666; + + svg { + width: 16px; + height: 16px; + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; + } + } + } + + .slimstat-select-wrapper.slimstat-select-open .slimstat-select-arrow { + transform: rotate(180deg); + } + + .slimstat-select-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: #fff; + border: 1px solid $brand; + border-top: none; + border-radius: 0 0 5px 5px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + z-index: 1000; + max-height: 200px; + overflow: hidden; + } + + .slimstat-select-search { + position: relative; + padding: 8px; + + input { + width: 100%; + padding: 6px 12px; + border: 1px solid #ddd; + border-radius: 3px; + font-size: 14px; + outline: none; + + &:focus { + border-color: $brand; + box-shadow: 0 0 0 1px $brand; + } + } + } + + .slimstat-select-options { + max-height: 150px; + overflow-y: auto; + } + + .slimstat-select-option { + display: flex; + width: 100%; + padding: 8px 12px; + border: none; + background: none; + text-align: left; + font-size: 14px; + color: #555; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: #f5f5f5; + } + + &.slimstat-selected { + background-color: $brand; + color: #fff; + } + + &.slimstat-hidden { + display: none; + } + } + + .slimstat-select-no-results { + padding: 12px; + text-align: center; + color: #999; + font-style: italic; + font-size: 14px; + } + + .slimstat-select-loading { + padding: 12px; + text-align: center; + color: #666; + font-size: 14px; + + &:before { + content: "\\f463"; + font-family: dashicons; + display: inline-block; + animation: spin 1s linear infinite; + margin-right: 5px; + } + } +} +.slimstat-option-icon { + display: inline-block; + vertical-align: middle; + margin-right: 5px; + flex-shrink: 0; + border-radius: 3px; + object-fit: cover; +} +.slimstat-select-option { + align-items: center; + + .slimstat-option-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} +.slimstat-select-text .slimstat-option-icon { + margin-right: 5px; + vertical-align: middle; + display: inline-block; +} +.rtl .slimstat-option-icon { + margin-right: 0; + margin-left: 5px; +} +.rtl .slimstat-select-text .slimstat-option-icon { + margin-right: 0; + margin-left: 5px; +} \ No newline at end of file @@ -83,21 +83,10 @@ 'description' => __('Customize the information displayed when activating the option here above: <strong>hits</strong> refers to the total amount of pageviews, regardless of the user; <strong>(unique) IPs</strong> displays the amount of distinct IP addresses tracked in the given time range.', 'wp-slimstat'), ], - // General - Database - 'general_database_header' => [ - 'title' => __('Database', 'wp-slimstat'), - 'type' => 'section_header', - ], - 'auto_purge' => [ - 'title' => __('Data Retention', 'wp-slimstat'), - 'type' => 'integer', - 'after_input_field' => __('days', 'wp-slimstat'), - 'description' => __('Enable a daily cron job to erase or archive (see option here below) pageviews older than the number of days specified here. You can enter <strong>0</strong> (the number zero) if you want to disable this feature.', 'wp-slimstat'), - ], - 'auto_purge_delete' => [ - 'title' => __('Archive Records', 'wp-slimstat'), + 'display_notifications' => [ + 'title' => __('Slimstat Notifications', 'wp-slimstat'), 'type' => 'toggle', - 'description' => __('If server space is not an issue for you, use this option to archive pageviews to a separate table, instead of deleting them. This will increase performance by reducing the amount of data to process in the main table, while allowing you to access your data at a later time, if needed. Please note that the archive table (<strong>wp_slim_stats_archive</strong>) will be <strong>DELETED</strong> along with all the other tables, when you uninstall Slimstat. Make sure to backup your data before you proceed.', 'wp-slimstat'), + 'description' => __('Display important notifications inside the plugin, such as new version releases, feature updates, news, and special offers.', 'wp-slimstat'), ], ], ], @@ -105,43 +94,167 @@ 2 => [ 'title' => __('Tracker', 'wp-slimstat'), 'rows' => [ - // Tracker - Data Protection - 'privacy_header' => [ - 'title' => __('Data Protection', 'wp-slimstat'), + // Tracker - Consent Management + 'consent_management_header' => [ + 'title' => __('Consent Management', 'wp-slimstat'), 'type' => 'section_header', ], - 'anonymize_ip' => [ - 'title' => __('Privacy Mode', 'wp-slimstat'), - 'type' => 'toggle', - 'description' => __("Mask your visitors' IP addresses (by converting the last number into a zero) and do not track their browser fingerprint, to comply with European privacy laws.", 'wp-slimstat'), - ], - 'set_tracker_cookie' => [ - 'title' => __('Set Cookie', 'wp-slimstat'), - 'type' => 'toggle', - 'description' => __('Disable this option if, for legal or security reasons, you do not want Slimstat to assign a <a href="https://en.wikipedia.org/wiki/HTTP_cookie" target="_blank">cookie</a> to your visitors. Please note that by deactivating this feature, Slimstat will not be able to identify returning visitors as such.', 'wp-slimstat'), - ], - 'display_opt_out' => [ - 'title' => __('Allow Opt-out', 'wp-slimstat'), - 'type' => 'toggle', - 'description' => __("The European <a href='https://en.wikipedia.org/wiki/General_Data_Protection_Regulation' target='_blank'>General Data Protection Regulation (GDPR)</a> requires website owners to provide a way for their visitors to opt-out of tracking. By enabling this option, the message here below will be displayed to all users who don't have the corresponding cookie set.", 'wp-slimstat'), - ], + 'gdpr_enabled' => [ + 'title' => __('GDPR Compliance Mode', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __('<strong>GDPR Compliance:</strong> When enabled, SlimStat requires user consent before tracking (except in Anonymous Tracking mode). When disabled, tracking operates normally without consent checks.<br/><br/><strong>Enabled:</strong> (Recommended for EU/EEA) Tracking requires consent unless Anonymous Tracking mode is active. This ensures GDPR compliance.<br/><strong>Disabled:</strong> Normal tracking without consent checks. Use this only if you are not subject to GDPR regulations (e.g., non-EU websites with no EU visitors).', 'wp-slimstat'), + ], + 'consent_integration' => [ + 'title' => __('Consent Plugin Integration', 'wp-slimstat'), + 'type' => 'select', + 'description' => __('<strong>GDPR Compliance:</strong> Integrate with a Consent Management Platform (CMP) to ensure tracking only occurs with user consent.<br/><br/><strong>SlimStat Consent Banner:</strong> (Recommended) Use SlimStat\'s built-in banner with customizable messaging and server-side consent tracking.<br/><strong>Via WP Consent API:</strong> Integrates with CMPs supporting WordPress Consent API (Complianz, CookieYes, etc.). Server-side consent checking available for both modes.', 'wp-slimstat'), + 'select_values' => [ + 'slimstat_banner' => __('SlimStat Consent Banner (Recommended)', 'wp-slimstat'), + 'wp_consent_api' => __('Via WP Consent API', 'wp-slimstat'), + // 'real_cookie_banner' => __('Real Cookie Banner', 'wp-slimstat'), + ], + 'conditional' => [ + 'field' => 'gdpr_enabled', + 'type' => 'checked', + ], + ], + 'slimstat_banner_header' => [ + 'title' => __('SlimStat Consent Banner', 'wp-slimstat'), + 'type' => 'section_header', + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,equals', + 'value' => '|||slimstat_banner', + ], + ], 'opt_out_cookie_names' => [ 'title' => __('Opt-out Cookies', 'wp-slimstat'), 'type' => 'textarea', 'description' => __("If you are already using another tool to monitor which users opt-out of tracking, and assuming that this tool sets its own cookie to remember their selection, you can enter the cookie names and values in this field to let Slimstat comply with their choice. Please use the following format: <code>cookie_name=value</code>. Slimstat will track any visitors who either don't send a cookie with that name, or send a cookie whose value <strong>does not CONTAIN</strong> the string you specified. If your tool uses structured values like JSON or similar encodings, find the substring related to tracking and enter that as the value here below. For example, <a href='https://wordpress.org/plugins/smart-cookie-kit/' target='_blank'>Smart Cookie Kit</a> uses something like <code>{\"settings\":{\"technical\":true,\"slimstat\":false,\"profiling\":false},\"ver\":\"2.0.0\"}</code>, so your pair should look like: <code>CookiePreferences-your.website.here=\"slimstat\":false</code>. Separate multiple pairs with commas.", 'wp-slimstat'), + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,equals', + 'value' => '|||', + ], ], 'opt_in_cookie_names' => [ 'title' => __('Opt-in Cookies', 'wp-slimstat'), 'type' => 'textarea', 'description' => __('Similarly to the option here above, you can configure Slimstat to work with an opt-in mechanism. Please use the following format: <code>cookie_name=value</code>. Slimstat will only track visitors who send a cookie whose value <strong>CONTAINS</strong> the string you specified. Separate multiple pairs with commas.', 'wp-slimstat'), + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,equals', + 'value' => '|||', + ], ], - 'opt_out_message' => [ - 'title' => __('Opt-out Message', 'wp-slimstat'), - 'type' => 'textarea', - 'rows' => 4, - 'use_tag_list' => false, - 'use_code_editor' => 'htmlmixed', - 'description' => __('Customize the message displayed to your visitors here below. Match your website styles and layout by adding the appropriate HTML markup to your message.', 'wp-slimstat'), + 'opt_out_message' => [ + 'title' => __('Consent Banner Message', 'wp-slimstat'), + 'type' => 'rich_text', + 'after_input_field' => '', + 'description' => __('Content displayed inside the SlimStat consent banner. Basic HTML (p, a, strong, em) is allowed. Use the editor above to format your message.', 'wp-slimstat'), + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,equals', + 'value' => '|||slimstat_banner', + ], + ], + 'gdpr_accept_button_text' => [ + 'title' => __('Accept Button Label', 'wp-slimstat'), + 'type' => 'text', + 'before_input_field' => '', + 'after_input_field' => '', + 'description' => __('Leave empty to use the default "Accept" text.', 'wp-slimstat'), + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,equals', + 'value' => '|||slimstat_banner', + ], + ], + 'gdpr_decline_button_text' => [ + 'title' => __('Decline Button Label', 'wp-slimstat'), + 'type' => 'text', + 'before_input_field' => '', + 'after_input_field' => '', + 'description' => __('Leave empty to use the default "Deny" text.', 'wp-slimstat'), + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,equals', + 'value' => '|||slimstat_banner', + ], + ], + 'gdpr_theme_mode' => [ + 'title' => __('Banner Theme Mode', 'wp-slimstat'), + 'type' => 'select', + 'description' => __("Choose the theme mode for the GDPR consent banner. <strong>Light</strong> uses light colors, <strong>Dark</strong> uses dark colors, and <strong>Auto</strong> follows the user's system preference.", 'wp-slimstat'), + 'select_values' => [ + 'light' => __('Light Mode', 'wp-slimstat'), + 'dark' => __('Dark Mode', 'wp-slimstat'), + 'auto' => __('Auto (Follow System)', 'wp-slimstat'), + ], + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,equals', + 'value' => '|||slimstat_banner', + ], + ], +/* + 'consent_level_integration' => [ + 'title' => __('Consent Category', 'wp-slimstat'), + 'type' => 'select', + 'description' => __('Select the consent category SlimStat should belong to. Tracking will only occur if the visitor grants consent for this specific category.<br/><br/><strong>Functional:</strong> Essential website functionality. Not typically used for analytics.<br/><strong>Statistics-Anonymous:</strong> Anonymous analytics only. Use this if you have enabled Anonymous Tracking mode OR configured SlimStat to be cookie-less with anonymized/hashed IPs.<br/><strong>Statistics:</strong> (Default & Recommended) Standard analytics tracking. Appropriate for both anonymous and standard tracking modes. Real Cookie Banner and most CMPs recommend this category for analytics plugins.<br/><strong>Marketing:</strong> Advertising and user profiling. Not applicable to SlimStat core functionality.<br/><br/><strong>Note for Real Cookie Banner:</strong> Make sure to configure SlimStat in the Real Cookie Banner plugin settings and assign it to the same category selected here.', 'wp-slimstat'), + 'select_values' => [ + 'functional' => __('Functional', 'wp-slimstat'), + 'statistics-anonymous' => __('Statistics-Anonymous', 'wp-slimstat'), + 'statistics' => __('Statistics', 'wp-slimstat'), + 'marketing' => __('Marketing', 'wp-slimstat'), + ], + 'conditional' => [ + 'field' => 'gdpr_enabled,consent_integration', + 'type' => 'checked,in', + 'value' => '|||wp_consent_api,real_cookie_banner', + ], + ], +*/ + + // Tracker - Data Protection + 'privacy_header' => [ + 'title' => __('Data Protection', 'wp-slimstat'), + 'type' => 'section_header', + ], +/* + 'anonymous_tracking' => [ + 'title' => __('Anonymous Tracking Mode', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __('<strong>GDPR-Safe Mode:</strong> When enabled, SlimStat operates in strict GDPR-compliant mode.<br/><br/><strong>Before Consent:</strong> Tracks anonymously (hashed IPs, no cookies, no username/email)<br/><strong>After Consent:</strong> Upgrades to full tracking (real IPs, cookies, user identification)<br/><br/>This mode is recommended if you want to track all visitors while staying GDPR-compliant. Anonymous data is collected without consent, then upgraded when consent is granted.', 'wp-slimstat'), + 'conditional' => [ + 'field' => 'gdpr_enabled', + 'type' => 'checked', + ], + ], + 'do_not_track' => [ + 'title' => __('Respect Do Not Track (DNT)', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __('<strong>Privacy Enhancement:</strong> Honor the DNT browser header. When a visitor has DNT enabled in their browser, NO tracking occurs (not even anonymous tracking).<br/><br/>GDPR does not require this, but it demonstrates respect for user privacy preferences. Recommended for privacy-focused websites.', 'wp-slimstat'), + 'conditional' => [ + 'field' => 'gdpr_enabled', + 'type' => 'checked', + ], + ], +*/ + 'anonymize_ip' => [ + 'title' => __('Anonymize IP Addresses', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __('<strong>GDPR Privacy Protection:</strong> Masks IP addresses before storage (IPv4: 192.168.1.x → 192.168.1.0 / IPv6: last 80 bits removed).<br/><br/>Anonymized IPs cannot identify individual users but still provide useful geographic and network data. <strong>Recommended</strong> for GDPR compliance when not using IP hashing.', 'wp-slimstat'), + ], + 'hash_ip' => [ + 'title' => __('Hash IP Addresses', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __('<strong>GDPR-Compliant Visitor Counting:</strong> Creates one-way hash from IP + User Agent + daily salt. Hash changes daily, preventing long-term tracking.<br/><br/><strong>Benefits:</strong> Count unique visitors without storing real IPs or using cookies. Original IP cannot be recovered from hash. <strong>Recommended</strong> for GDPR compliance.', 'wp-slimstat'), + ], + 'set_tracker_cookie' => [ + 'title' => __('Set Tracking Cookie', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __('<strong>PII Warning:</strong> Cookies are Personally Identifiable Information under GDPR. Enabling this option requires user consent.<br/><br/><strong>When Disabled:</strong> Cookie-less tracking (more privacy, less accurate return visitor detection)<br/><strong>When Enabled:</strong> Sets a cookie to track returning visitors (better accuracy, requires consent)<br/><br/>Cookies automatically respect consent settings and use Secure, HttpOnly, and SameSite flags for security.', 'wp-slimstat'), ], // Tracker - Link Tracking @@ -165,21 +278,26 @@ 'title' => __('Third-party Libraries', 'wp-slimstat'), 'type' => 'section_header', ], - 'enable_maxmind' => [ - 'title' => __('GeoIP Database Source', 'wp-slimstat'), - 'after_input_field' => ((!empty($_POST['options']['enable_maxmind']) && 'disable' != sanitize_text_field($_POST['options']['enable_maxmind'])) || (empty($_POST['options']['enable_maxmind']) && 'disable' != wp_slimstat::$settings['enable_maxmind'])) ? '<input type="hidden" id="slimstat-geoip-nonce" value="' . wp_create_nonce('wp_rest') . '" /><a href="#" id="slimstat-update-geoip-database" class="button-secondary noslimstat" style="vertical-align: middle" data-error-message="' . __('An error occurred while updating the GeoIP database.', 'wp-slimstat') . '">' . __('Update Database', 'wp-slimstat') . '</a> <a href="#" id="slimstat-check-geoip-database" class="button-secondary noslimstat" style="vertical-align: middle" data-error-message="' . __('An error occurred while updating the GeoIP database.', 'wp-slimstat') . '">' . __('Check Database', 'wp-slimstat') . '</a>' : '', - 'type' => 'select', - 'select_values' => [ - 'disable' => __('Disable', 'wp-slimstat'), - 'no' => __('Use the JsDelivr', 'wp-slimstat'), - 'on' => __('Use the MaxMind server with your own license key', 'wp-slimstat'), - ], - 'description' => __('Choose a service to update the GeoIP database to ensure your geographic information is accurate and up-to-date.', 'wp-slimstat') . '<br />' . __('<b>Note: </b>If the database file is missing, it will be downloaded when you save the settings.', 'wp-slimstat'), + 'geolocation_provider' => [ + 'title' => __('Geolocation Provider', 'wp-slimstat'), + 'type' => 'select', + 'select_values' => [ + 'maxmind' => __('MaxMind GeoLite2 (recommended)', 'wp-slimstat'), + 'dbip' => __('DB-IP City Lite (free)', 'wp-slimstat'), + 'cloudflare' => __('Cloudflare Header', 'wp-slimstat'), + ], + 'description' => __('<strong>Choose how Slimstat resolves visitor locations:</strong><br /><strong>DB-IP City Lite</strong> – Free, no license required. Slimstat downloads a local database and updates it automatically in the background after you save settings. You can also run the update manually using the button below. Works for arbitrary IPs in reports.<br /><strong>MaxMind GeoLite2</strong> – Requires a free MaxMind license key. City vs Country precision affects database size and download time. Updates run in the background after saving; you can also update manually. If PHP Phar is disabled on your server, please upload the .mmdb file manually to wp-content/uploads/wp-slimstat/.<br /><strong>Cloudflare Header</strong> – No database needed. Slimstat reads the HTTP_CF_IPCOUNTRY header set by Cloudflare for the current request only. It won\'t resolve arbitrary test IPs (like 8.8.8.8). Make sure "IP Geolocation" is enabled in your Cloudflare dashboard and your site is actually proxied through Cloudflare.', 'wp-slimstat'), ], 'maxmind_license_key' => [ 'title' => __('MaxMind License Key', 'wp-slimstat'), 'type' => 'text', - 'description' => __('To be able to automatically download and update the MaxMind GeoLite2 database, you must sign up on <a href="https://dev.maxmind.com/geoip/geoip2/geolite2/" target="_blank">MaxMind GeoLite2</a> and create a license key. Then enter your license key in this field. Disable- and re-enable MaxMind Geolocation above to activate the license key. Note: It takes a couple of minutes after you created the license key to get it activated on the MaxMind website.', 'wp-slimstat'), + 'description' => __('Enter your MaxMind license key to enable automatic downloads of the GeoLite2 database. The license key should be 16-40 characters containing only letters, numbers, and underscores. Required only if you select MaxMind as the provider. <strong>Important:</strong> If the PHP Phar extension is not available on your server, automatic extraction will fail—upload the .mmdb file manually to wp-content/uploads/wp-slimstat/.', 'wp-slimstat'), + ], + 'geolocation_db_actions' => [ + 'title' => __('Geolocation Database', 'wp-slimstat'), + 'after_input_field' => '<input type="hidden" id="slimstat-geoip-nonce" value="' . wp_create_nonce('slimstat_geoip_action') . '" /><a href="#" id="slimstat-update-geoip-database" class="button-secondary noslimstat" style="vertical-align: middle" data-error-message="' . __('An error occurred while updating the GeoIP database.', 'wp-slimstat') . '">' . __('Update Database', 'wp-slimstat') . '</a> <a href="#" id="slimstat-check-geoip-database" class="button-secondary noslimstat" style="vertical-align: middle" data-error-message="' . __('An error occurred while updating the GeoIP database.', 'wp-slimstat') . '">' . __('Check Database', 'wp-slimstat') . '</a>', + 'type' => 'plain-text', + 'description' => __('Download or refresh the selected geolocation database. <strong>DB-IP/MaxMind only</strong>: "Update Database" runs it now; after saving settings, Slimstat also schedules a background update. "Check Database" verifies that the file exists and is readable. <strong>Cloudflare</strong>: No database is required—the header is used at request time.', 'wp-slimstat'), ], 'enable_browscap' => [ 'title' => __('Browscap Library', 'wp-slimstat'), @@ -197,7 +315,7 @@ 'type' => 'toggle', 'custom_label_on' => __('Country', 'wp-slimstat'), 'custom_label_off' => __('City', 'wp-slimstat'), - 'description' => __("Slimstat determines your visitors' Country of origin through third-party libraries. This information is available in two precision levels: country and city. By default, Slimstat will install the country precision level. Use this option to switch to the more granular level, if you don't mind its 60 Mb average size.", 'wp-slimstat'), + 'description' => __('Choose between Country and City precision. City uses a larger database and may take longer to download (and more disk space). Country is smaller and faster. Applies to DB‑IP and MaxMind; Cloudflare always provides country only.', 'wp-slimstat'), ], 'session_duration' => [ 'title' => __('Visit Duration', 'wp-slimstat'), @@ -210,6 +328,12 @@ 'type' => 'toggle', 'description' => __("Reset your visitors' visit duration every time they access a new page within the current visit.", 'wp-slimstat'), ], + + // Tracker - Performance + 'performance_header' => [ + 'title' => __('Performance', 'wp-slimstat'), + 'type' => 'section_header', + ], 'enable_cdn' => [ 'title' => __('Enable CDN', 'wp-slimstat'), 'type' => 'toggle', @@ -236,6 +360,32 @@ 'title' => __('Add the following code to all the non-WordPress pages you would like to track, right before the closing BODY tag. Please make sure to change the protocol of all the URLs to HTTPS, if you external site is using a secure channel.', 'wp-slimstat'), 'markup' => '<pre style="max-width:100%"><script type="text/javascript">\n/* <![CDATA[ */\nvar SlimStatParams = { ajaxurl: "' . ((('on' == (wp_slimstat::$settings['ajax_relative_path'] ?? '')) ? admin_url('admin-ajax.php', 'relative') : admin_url('admin-ajax.php'))) . '" };\n/* ]]> */\n</script>\n<script type="text/javascript" src="https://cdn.jsdelivr.net/wp/wp-slimstat/trunk/wp-slimstat.min.js"></script></pre>', ], + + // Tracker - Third-party Libraries + 'third_party_libraries_header' => [ + 'title' => __('Third-party Libraries', 'wp-slimstat'), + 'type' => 'section_header', + ], + 'enable_maxmind' => [ + 'title' => __('GeoIP Database Source', 'wp-slimstat'), + 'type' => 'select', + 'select_values' => [ + 'disable' => __('Disable', 'wp-slimstat'), + 'no' => __('Use the JsDelivr', 'wp-slimstat'), + 'on' => __('Use the MaxMind server with your own license key', 'wp-slimstat'), + ], + 'description' => __('Choose a service to update the GeoIP database to ensure your geographic information is accurate and up-to-date.', 'wp-slimstat') . '<br />' . __('<b>Note: </b>If the database file is missing, it will be downloaded when you save the settings.', 'wp-slimstat'), + ], + 'maxmind_license_key' => [ + 'title' => __('MaxMind License Key', 'wp-slimstat'), + 'type' => 'text', + 'description' => __('To be able to automatically download and update the MaxMind GeoLite2 database, you must sign up on <a href="https://dev.maxmind.com/geoip/geoip2/geolite2/" target="_blank">MaxMind GeoLite2</a> and create a license key. Then enter your license key in this field. Disable- and re-enable MaxMind Geolocation above to activate the license key. Note: It takes a couple of minutes after you created the license key to get it activated on the MaxMind website.', 'wp-slimstat'), + ], + 'enable_browscap' => [ + 'title' => __('Browscap Library', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __("We are contributing to the <a href='https://browscap.org/' target='_blank'>Browscap Capabilities Project</a>, which we use to decode your visitors' user agent string into browser name and operating system. We use an <a href='https://github.com/slimstat/browscap-cache' target='_blank'>optimized version of their data structure</a>, for improved performance. When enabled, Slimstat uses this library in addition to the built-in heuristic function, to determine your visitors' browser information. Updates are downloaded automatically every week, when available.", 'wp-slimstat') . (empty(\SlimStat\Services\Browscap::$browscap_local_version) ? '' : ' ' . sprintf(__('You are currently using version %s.', 'wp-slimstat'), '<strong>' . \SlimStat\Services\Browscap::$browscap_local_version . '</strong>')), + ], ], ], @@ -538,6 +688,23 @@ 6 => [ 'title' => __('Maintenance', 'wp-slimstat'), 'rows' => [ + // Maintenance - Data Retention + 'maintenance_data_retention_header' => [ + 'title' => __('Data Retention & Auto-Purge', 'wp-slimstat'), + 'type' => 'section_header', + ], + 'auto_purge' => [ + 'title' => __('Retention Period', 'wp-slimstat'), + 'type' => 'integer', + 'after_input_field' => __('days', 'wp-slimstat'), + 'description' => __('<strong>GDPR Compliance:</strong> Automatically purge data older than the specified number of days. This process runs twice daily via WordPress cron to keep your database clean and maintain GDPR compliance.<br/><br/><strong>Recommended:</strong> <strong>420 days (14 months)</strong> - Complies with ePrivacy Directive and most GDPR interpretations. This ensures data is automatically removed after a reasonable retention period.<br/><strong>Warning:</strong> Retaining data longer than 14 months may require additional legal justification and a clear Data Processing Agreement (DPA) under GDPR Article 5(1)(e) (Storage Limitation Principle). Failing to comply can result in significant fines.<br/><br/>Set to <strong>0</strong> to disable automatic purging (<strong>strongly discouraged</strong> for GDPR compliance, as unlimited retention requires a very strong and documented legal justification).', 'wp-slimstat'), + ], + 'auto_purge_delete' => [ + 'title' => __('Archive Mode', 'wp-slimstat'), + 'type' => 'toggle', + 'description' => __('<strong>How to handle old data:</strong><br/><br/><strong>Enabled (Archive):</strong> Old records are moved to separate archive tables (<code>wp_slim_stats_archive</code>, <code>wp_slim_events_archive</code>) instead of being permanently deleted. This improves query performance by keeping the main tables smaller, while still allowing you to access historical data if needed. <strong>Note:</strong> Archived data still counts as data retention under GDPR requirements.<br/><br/><strong>Disabled (Delete):</strong> Old records are permanently deleted from the database. This is the most GDPR-compliant approach and frees up database space immediately. <strong>Warning:</strong> Deleted data cannot be recovered.<br/><br/><strong>Important:</strong> Archive tables are <strong>permanently deleted</strong> when you uninstall SlimStat. Always <strong>backup your data</strong> before uninstalling if you need to retain it.', 'wp-slimstat'), + ], + // Maintenance - Troubleshooting 'maintenance_troubleshooting_header' => [ 'title' => __('Troubleshooting', 'wp-slimstat'), @@ -600,6 +767,7 @@ ], ]; + // Allow third-party tools to add their own settings $settings = apply_filters('slimstat_options_on_page', $settings); @@ -666,40 +834,40 @@ } } - // MaxMind Library - if (!empty($_POST['options']['enable_maxmind']) || !empty($_POST['options']['geolocation_country'])) { - $pack = ('on' == $_POST['options']['geolocation_country']) ? 'country' : 'city'; - $enableMaxmind = sanitize_text_field($_POST['options']['enable_maxmind']); - $licenseKey = empty($_POST['options']['maxmind_license_key']) ? '' : sanitize_text_field($_POST['options']['maxmind_license_key']); - - try { - $geographicProvider = new \SlimStat\Services\GeoService(); - $geographicProvider->setEnableMaxmind($enableMaxmind); - if ($geographicProvider->isGeoIPEnabled()) { - $result = $geographicProvider - ->setPack($pack) - ->setMaxmindLicense($licenseKey) - ->download(); - - if (false === $result['status']) { - $save_messages[] = $result['notice']; - } else { - $save_messages[] = __('The geolocation database has been installed on your server.', 'wp-slimstat'); - - // Save Settings - wp_slimstat::$settings['enable_maxmind'] = $enableMaxmind; - wp_slimstat::$settings['maxmind_license_key'] = $licenseKey; + // Geolocation settings save (provider-based) + if (isset($_POST['options']['geolocation_country']) || isset($_POST['options']['geolocation_provider']) || isset($_POST['options']['maxmind_license_key'])) { + $prevProvider = wp_slimstat::$settings['geolocation_provider'] ?? 'maxmind'; + $provider = sanitize_text_field($_POST['options']['geolocation_provider'] ?? $prevProvider); + $precision = ('on' === ($_POST['options']['geolocation_country'] ?? (wp_slimstat::$settings['geolocation_country'] ?? 'on'))) ? 'country' : 'city'; + $license = sanitize_text_field($_POST['options']['maxmind_license_key'] ?? (wp_slimstat::$settings['maxmind_license_key'] ?? '')); + + // Save settings + wp_slimstat::$settings['geolocation_provider'] = $provider; + wp_slimstat::$settings['geolocation_country'] = 'country' === $precision ? 'on' : 'no'; + wp_slimstat::$settings['maxmind_license_key'] = $license; + + // If provider needs a DB, schedule a background update to avoid timeouts during save + if ('cloudflare' !== $provider) { + try { + // Pass new settings explicitly since they haven't been saved to wp_slimstat::$settings yet + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, [ + 'dbPath' => \wp_slimstat::$upload_dir, + 'license' => $license, + 'precision' => $precision, + ]); + $dbExists = file_exists($service->getProvider()->getDbPath()); + + if (!$dbExists || $provider !== $prevProvider) { + // Schedule a single-run background job shortly after save + if (!wp_next_scheduled('wp_slimstat_update_geoip_database')) { + wp_schedule_single_event(time() + 10, 'wp_slimstat_update_geoip_database'); + } + $save_messages[] = __('The geolocation database update has been scheduled in the background. You can also use the Update Database button below to start it now.', 'wp-slimstat'); } - } else { - // Disable geographic database - wp_slimstat::$settings['enable_maxmind'] = 'disable'; + } catch (\Exception $e) { + $save_messages[] = $e->getMessage(); } - } catch (\Exception $e) { - $save_messages[] = $e->getMessage(); } - - // Save Settings - wp_slimstat::$settings['geolocation_country'] = sanitize_text_field($_POST['options']['geolocation_country']); } // Browscap Library @@ -726,13 +894,21 @@ } // All other options - foreach ($_POST['options'] as $a_post_slug => $a_post_value) { + foreach (wp_unslash($_POST['options']) as $a_post_slug => $a_post_value) { if (empty($settings[$current_tab]['rows'][$a_post_slug]) || !empty($settings[$current_tab]['rows'][$a_post_slug]['readonly']) || in_array($settings[$current_tab]['rows'][$a_post_slug]['type'], ['section_header', 'plain-text']) || in_array($a_post_slug, ['enable_maxmind', 'enable_browscap'])) { continue; } if (isset($a_post_value)) { - wp_slimstat::$settings[$a_post_slug] = empty($settings[$current_tab]['rows'][$a_post_slug]['use_code_editor']) ? htmlspecialchars(sanitize_text_field($a_post_value)) : $a_post_value; + if ('rich_text' === $settings[$current_tab]['rows'][$a_post_slug]['type']) { + // Rich text editor: use wp_kses_post to sanitize HTML + wp_slimstat::$settings[$a_post_slug] = wp_kses_post($a_post_value); + } elseif (empty($settings[$current_tab]['rows'][$a_post_slug]['use_code_editor'])) { + wp_slimstat::$settings[$a_post_slug] = sanitize_text_field($a_post_value); + } else { + // Code editor content: strip all tags to prevent XSS + wp_slimstat::$settings[$a_post_slug] = wp_strip_all_tags($a_post_value); + } } // If the Network Settings add-on is enabled, there might be a switch to decide if this option needs to override what single sites have set @@ -748,6 +924,14 @@ } } + // Keep legacy banner toggle in sync with the selected consent integration. + $current_consent_integration = wp_slimstat::$settings['consent_integration'] ?? ''; + if ('slimstat_banner' === $current_consent_integration) { + wp_slimstat::$settings['use_slimstat_banner'] = 'on'; + } else { + wp_slimstat::$settings['use_slimstat_banner'] = 'off'; + } + // Allow third-party functions to manipulate the options right before they are saved wp_slimstat::$settings = apply_filters('slimstat_save_options', wp_slimstat::$settings); @@ -766,6 +950,26 @@ sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = '%sstats_resource_idx'", $GLOBALS[ 'wpdb' ]->prefix, $GLOBALS[ 'wpdb' ]->prefix) ); +$index_names = [ + $GLOBALS[ 'wpdb' ]->prefix . 'stats_resource_idx', + $GLOBALS[ 'wpdb' ]->prefix . 'stats_browser_idx', + $GLOBALS[ 'wpdb' ]->prefix . 'stats_searchterms_idx', + $GLOBALS[ 'wpdb' ]->prefix . 'stats_fingerprint_idx', +]; +$missing_indexes = []; +foreach ($index_names as $idx) { + $exists = wp_slimstat::$wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = '%s'", $GLOBALS[ 'wpdb' ]->prefix, $idx)); + if (empty($exists)) { + $missing_indexes[] = $idx; + } +} +if ([] !== $missing_indexes) { + echo '<div class="notice notice-warning"><b>' . esc_html__('Performance Notice:', 'wp-slimstat') . '</b> ' . sprintf( + esc_html__('The following DB indexes are missing and should be created for optimal performance: %s. Please visit the Slimstat settings or re-activate the plugin to trigger index creation.', 'wp-slimstat'), + '<code>' . esc_html(implode(', ', $missing_indexes)) . '</code>' + ) . '</div>'; +} + $tabs_html = ''; foreach ($settings as $a_tab_id => $a_tab_info) { if (!empty($a_tab_info['rows'])) { @@ -776,7 +980,7 @@ ?> <div class="backdrop-container"> <div class="wrap slimstat-config"> - <h2><?php _e('Settings', 'wp-slimstat') ?></h2> + <?php wp_slimstat_admin::get_template('header', ['is_pro' => wp_slimstat::pro_is_installed()]); ?> <ul class="nav-tabs"> <?php echo $tabs_html ?> </ul> @@ -824,16 +1028,29 @@ id="addon_network_settings_' . $a_setting_slug . '" data-size="mini" data-handle-width="50" data-on-color="warning" data-on-text="Network" data-off-text="Site">' : ''; - echo '<tr' . (0 == $i % 2 ? ' class="alternate"' : '') . '>'; + // Build conditional data attributes + $conditional_attrs = ''; + if (!empty($a_setting_info['conditional'])) { + $cond = $a_setting_info['conditional']; + $conditional_attrs = ' data-conditional-field="' . esc_attr($cond['field']) . '"'; + if (!empty($cond['type'])) { + $conditional_attrs .= ' data-conditional-type="' . esc_attr($cond['type']) . '"'; + } + if (isset($cond['value'])) { + $conditional_attrs .= ' data-conditional-value="' . esc_attr($cond['value']) . '"'; + } + } + + echo '<tr' . (0 == $i % 2 ? ' class="alternate"' : '') . $conditional_attrs . '>'; switch ($a_setting_info['type']) { case 'section_header': - echo '<td colspan="2" class="slimstat-options-section-header" id="wp-slimstat-' . sanitize_title($a_setting_info['title']) . '">' . $a_setting_info['title'] . '</td>'; + echo '<td colspan="2" class="slimstat-options-section-header"' . $conditional_attrs . ' id="wp-slimstat-' . sanitize_title($a_setting_info['title']) . '">' . $a_setting_info['title'] . '</td>'; break; case 'toggle': echo '<th scope="row"><label for="' . $a_setting_slug . '">' . $a_setting_info['title'] . '</label></th> <td> - <input type="hidden" value="no" name="options[' . $a_setting_slug . ']" id="' . $a_setting_slug . '"> + <input type="hidden" value="no" name="options[' . $a_setting_slug . ']"> <span class="block-element"> <input class="slimstat-checkbox-toggle" type="checkbox"' . $is_readonly . ' name="options[' . $a_setting_slug . ']" @@ -883,6 +1100,33 @@ </td>'; break; + case 'rich_text': + $editor_content = empty(wp_slimstat::$settings[$a_setting_slug]) ? '' : wp_kses_post(wp_slimstat::$settings[$a_setting_slug]); + $editor_settings = [ + 'textarea_name' => 'options[' . $a_setting_slug . ']', + 'textarea_rows' => 8, + 'media_buttons' => false, + 'teeny' => true, + 'tinymce' => [ + 'toolbar1' => 'bold,italic,underline,link,unlink,removeformat', + 'toolbar2' => '', + ], + ]; + if (!empty($is_readonly)) { + $editor_settings['readonly'] = true; + } + echo ' + <td colspan="2"> + <label for="' . $a_setting_slug . '">' . $a_setting_info['title'] . $network_override_checkbox . '</label> + <p class="description">' . $a_setting_info['description'] . '</p> + <p>'; + wp_editor($editor_content, $a_setting_slug, $editor_settings); + echo ' + <span class="description">' . $a_setting_info['after_input_field'] . '</span> + </p> + </td>'; + break; + case 'textarea': echo ' <td colspan="2"> @@ -891,7 +1135,7 @@ <p> <textarea class="large-text code' . $use_tag_list . '"' . $is_readonly . $use_code_editor . ' id="' . $a_setting_slug . '" - rows="' . $a_setting_info['rows'] . '" + rows="' . ($a_setting_info['rows'] ?? 4) . '" name="options[' . $a_setting_slug . ']">' . (empty(wp_slimstat::$settings[$a_setting_slug]) ? '' : stripslashes(wp_slimstat::$settings[$a_setting_slug])) . '</textarea> <span class="description">' . $a_setting_info['after_input_field'] . '</span> </p> @@ -925,3 +1169,35 @@ <?php endif ?> </div> </div> + +<?php +// Detect companion consent plugins to disable unavailable options in UI +if (!function_exists('is_plugin_active')) { + include_once ABSPATH . 'wp-admin/includes/plugin.php'; +} +$has_wp_consent_api = function_exists('is_plugin_active') && is_plugin_active('wp-consent-api/wp-consent-api.php'); +// $has_real_cookie_banner = function_exists('is_plugin_active') && is_plugin_active('real-cookie-banner/index.php'); +?> +<script> +(function($){ + $(function(){ + // Disable integrations that are not installed + var hasWpConsent = <?php echo $has_wp_consent_api ? 'true' : 'false'; ?>; + // var hasRCB = <?php echo isset($has_real_cookie_banner) && $has_real_cookie_banner ? 'true' : 'false'; ?>; + var $ci = $('#consent_integration'); + if(!hasWpConsent){ + $ci.find('option[value="wp_consent_api"]').prop('disabled', true); + // If currently selected value is wp_consent_api but plugin is not installed, reset to default + if($ci.val() === 'wp_consent_api') { + $ci.val('slimstat_banner').trigger('change'); + } + } + // if(!hasRCB){ $ci.find('option[value="real_cookie_banner"]').prop('disabled', true); } + + // Initialize conditional fields system (from admin.js) + if (typeof window.SlimStatConditionalFields !== 'undefined') { + window.SlimStatConditionalFields.init(); + } + }); +})(jQuery); +</script> @@ -1,20 +1,19 @@ <?php use SlimStat\Services\GeoService; - +use SlimStat\Components\DateRangeHelper; +use SlimStat\Services\Admin\Notification\NotificationFactory; class wp_slimstat_admin { - public static $screens_info = []; - - public static $config_url = ''; - - public static $current_screen = 'slimview1'; - - public static $page_location = 'slimstat'; - + public static $screens_info = []; + public static $config_url = ''; + public static $current_screen = 'slimview1'; + public static $page_location = 'slimstat'; public static $meta_user_reports = []; - - protected static $admin_notice = ''; + public static $settings = []; + public static $user_reports = []; + public static $admin_notice = ''; + public static $main_menu_slug = 'slimview1'; protected static $data_for_column = [ 'url' => [], @@ -38,9 +37,6 @@ // Action for reset layout add_action('admin_post_slimstat_reset_layout', ['wp_slimstat_admin', 'handle_reset_layout']); - // Load language files - load_plugin_textdomain('wp-slimstat', false, '/wp-slimstat/languages'); - // Define the default screens $has_network_reports = get_user_option('meta-box-order_slimstat_page_slimlayout-network', 1); @@ -80,6 +76,13 @@ 'capability' => 'can_view', 'callback' => [self::class, 'wp_slimstat_include_view'], ], + 'slimemail' => [ + 'is_report_group' => false, + 'show_in_sidebar' => true, + 'title' => wp_slimstat::pro_is_installed() ? __('Email Report', 'wp-slimstat') : __('Email Report (pro)', 'wp-slimstat'), + 'capability' => 'can_view', + 'callback' => [self::class, 'wp_slimstat_include_email_report'], + ], 'slimlayout' => [ 'is_report_group' => false, 'show_in_sidebar' => true, @@ -165,14 +168,9 @@ add_filter('wpmu_drop_tables', [self::class, 'drop_tables'], 10, 2); // Display a notice that hightlights this version's features - if (!empty($_GET['page']) && false !== strpos($_GET['page'], 'slimview')) { - if (!empty(self::$admin_notice) && 'on' == wp_slimstat::$settings['notice_latest_news'] && is_super_admin()) { - add_action('admin_notices', [self::class, 'show_latest_news']); - } + if (!empty($_GET['page']) && false !== strpos($_GET['page'], 'slimview') && (!empty(self::$admin_notice) && 'on' == wp_slimstat::$settings['notice_latest_news'] && is_super_admin())) { + add_action('admin_notices', [self::class, 'show_latest_news']); - if ('on' == wp_slimstat::$settings['notice_translate'] && is_super_admin()) { - add_filter('admin_notices', [self::class, 'show_translate_notice']); - } } // Remove spammers from the database @@ -183,6 +181,8 @@ // Add a menu to the admin bar if ('no' != wp_slimstat::$settings['use_separate_menu'] && is_admin_bar_showing()) { add_action('admin_bar_menu', [self::class, 'add_menu_to_adminbar'], 100); + add_action('admin_enqueue_scripts', [self::class, 'enqueue_adminbar_styles']); + add_action('wp_enqueue_scripts', [self::class, 'enqueue_adminbar_styles']); } if (function_exists('is_network_admin') && !is_network_admin()) { @@ -212,8 +212,20 @@ } } + // Initialize Reports system for SlimStat pages and AJAX requests + $is_slimstat_page = (!empty($_GET['page']) && 0 === strpos($_GET['page'], 'slim')); + $is_slimstat_ajax = (!empty($_POST['action']) && ( + 'slimstat_load_report' === $_POST['action'] || + 'slimstat_get_live_analytics_data' === $_POST['action'] + )); + + if ($is_slimstat_page || $is_slimstat_ajax) { + // Initialize the new Reports system FIRST before legacy system loads + \SlimStat\Reports\Bootstrap::get_instance()->init(); + } + // Load the library of functions to generate the reports - if ((!empty($_GET['page']) && 0 === strpos($_GET['page'], 'slim')) || (!empty($_POST['action']) && 'slimstat_load_report' == $_POST['action'])) { + if ($is_slimstat_page || (!empty($_POST['action']) && 'slimstat_load_report' == $_POST['action'])) { include_once(plugin_dir_path(__FILE__) . 'view/wp-slimstat-reports.php'); wp_slimstat_reports::init(); @@ -242,16 +254,24 @@ // AJAX Handlers if (defined('DOING_AJAX') && DOING_AJAX) { - add_action('wp_ajax_slimstat_notice_latest_news', [self::class, 'notices_handler']); - add_action('wp_ajax_slimstat_notice_geolite', [self::class, 'notices_handler']); - add_action('wp_ajax_slimstat_notice_browscap', [self::class, 'notices_handler']); - add_action('wp_ajax_slimstat_notice_caching', [self::class, 'notices_handler']); - add_action('wp_ajax_slimstat_notice_translate', [self::class, 'notices_handler']); - - add_action('wp_ajax_slimstat_manage_filters', [self::class, 'manage_filters']); - add_action('wp_ajax_slimstat_delete_pageview', [self::class, 'delete_pageview']); - add_action('wp_ajax_slimstat_update_geoip_database', [self::class, 'update_geoip_database']); - add_action('wp_ajax_slimstat_check_geoip_database', [self::class, 'check_geoip_database']); + $ajax_actions = [ + 'slimstat_notice_latest_news' => 'notices_handler', + 'slimstat_notice_geolite' => 'notices_handler', + 'slimstat_notice_browscap' => 'notices_handler', + 'slimstat_notice_caching' => 'notices_handler', + 'slimstat_manage_filters' => 'manage_filters', + 'slimstat_delete_pageview' => 'delete_pageview', + 'slimstat_update_geoip_database' => 'update_geoip_database', + 'slimstat_check_geoip_database' => 'check_geoip_database', + 'slimstat_get_filter_options' => 'get_filter_options', + 'slimstat_get_online_visitors' => 'get_online_visitors', + ]; + foreach ($ajax_actions as $action => $handler) { + add_action('wp_ajax_' . $action, [self::class, $handler]); + } + + // Live Analytics AJAX handler is registered via init_hooks() in Bootstrap + // No need to call it separately here - it's already registered } // Schedule a daily cron job to purge the data @@ -259,12 +279,90 @@ wp_schedule_event(time(), 'twicedaily', 'wp_slimstat_purge'); } + // Schedule a daily cron job to regenerate IP hashing salt (for GDPR compliance) + if (!wp_next_scheduled('wp_slimstat_generate_daily_salt')) { + wp_schedule_event(time(), 'daily', 'wp_slimstat_generate_daily_salt'); + } + // Schedule a weekly cron job to update geoip database automatically if (!wp_next_scheduled('wp_slimstat_update_geoip_database')) { $nextRunInterval = wp_slimstat::get_schedule_interval('weekly'); wp_schedule_event(time() + $nextRunInterval, 'weekly', 'wp_slimstat_update_geoip_database'); } + // Fallback: if WP-Cron is disabled or scheduling failed, trigger a non-blocking direct update + // This ensures environments with DISABLE_WP_CRON still receive GeoIP database updates + $cron_disabled = (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON) || !wp_next_scheduled('wp_slimstat_update_geoip_database'); + if ($cron_disabled) { + // Update if DB is missing or last update is older than the most recent past scheduled window + $last_update = (int) get_option('slimstat_last_geoip_dl', 0); + + // Calculate the most recent "first Tuesday + 2 days" that has already passed + $this_month_update = strtotime('first Tuesday of this month') + (86400 * 2); + $current_time = time(); + + // If this month's update window hasn't arrived yet, use last month's window + if ($current_time < $this_month_update) { + $this_update = strtotime('first Tuesday of last month') + (86400 * 2); + } else { + $this_update = $this_month_update; + } + + $db_missing = false; + try { + $provider = \wp_slimstat::$settings['geolocation_provider'] ?? 'maxmind'; + $uses_db = in_array($provider, ['maxmind', 'dbip'], true); + if ($uses_db) { + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + $db_missing = !file_exists($service->getProvider()->getDbPath()); + } + } catch (\Throwable $e) { + // If any error occurs while checking, consider DB missing to be safe + $db_missing = true; + } + + if ($db_missing || $last_update < $this_update) { + // Fire admin-ajax in a non-blocking way to run the existing update handler + $ajax_url = admin_url('admin-ajax.php'); + // Forward only WordPress authentication cookies for security + $cookie_header = ''; + if (!headers_sent() && $_COOKIE !== [] && is_array($_COOKIE)) { + $pairs = []; + // Only forward WordPress authentication cookies + $allowed_cookie_prefixes = [ + 'wordpress_logged_in_', + 'wordpress_sec_', + 'wp-settings-', + 'wp-settings-time-', + ]; + foreach ($_COOKIE as $k => $v) { + $is_allowed = false; + foreach ($allowed_cookie_prefixes as $prefix) { + if (strpos($k, $prefix) === 0) { + $is_allowed = true; + break; + } + } + if ($is_allowed) { + $pairs[] = rawurlencode($k) . '=' . rawurlencode(sanitize_text_field(wp_unslash($v))); + } + } + $cookie_header = implode('; ', $pairs); + } + $args = [ + 'timeout' => 0.01, + 'blocking' => false, + 'body' => [ + 'action' => 'slimstat_update_geoip_database', + 'security' => wp_create_nonce('slimstat_geoip_action'), + ], + 'headers' => $cookie_header !== '' && $cookie_header !== '0' ? ['Cookie' => $cookie_header] : [], + ]; + // Best-effort call; ignore response + wp_safe_remote_post($ajax_url, $args); + } + } + // Add style to the admin menu add_action('admin_head', [self::class, 'styling_admin_menu']); @@ -274,10 +372,36 @@ // Add lock export button in report header add_filter('slimstat_report_header_buttons', fn ($_header_buttons, $_report_id) => self::add_lock_export_button($_header_buttons, $_report_id), 10, 2); - // Add header to settings and customize and settings page - add_action('admin_notices', function () { - self::add_header(); - }); + $index_checks = [ + ['option' => 'slimstat_country_dt_indexed', 'key' => 'idx_country_dt'], + ['option' => 'slimstat_dt_screen_indexed', 'key' => 'idx_dt_screen_width_screen_height'], + ['option' => 'slimstat_dt_browser_indexed', 'key' => 'idx_dt_browser_browser_version'], + ['option' => 'slimstat_dt_platform_indexed', 'key' => 'idx_dt_platform'], + ]; + foreach ($index_checks as $idx) { + $exists = wp_slimstat::$wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = '%s'", $GLOBALS['wpdb']->prefix, $idx['key'])); + if (!empty($exists)) { + update_option($idx['option'], 'yes'); + } + } + + self::register_country_dt_index_hooks(); + self::register_dt_screen_index_hooks(); + self::register_dt_browser_index_hooks(); + self::register_dt_platform_index_hooks(); + self::register_dt_out_index_hooks(); + + // Register the combined notice + add_action('admin_notices', ['wp_slimstat_admin', 'show_indexes_notice']); + + // Initialize notification system + if (class_exists('SlimStat\\Services\\Admin\\Notification\\NotificationManager')) { + new \SlimStat\Services\Admin\Notification\NotificationManager(); + } + // Initialize cron manager for notifications + if (class_exists('SlimStat\\Services\\CronEventManager')) { + new \SlimStat\Services\CronEventManager(); + } } // END: init @@ -362,6 +486,34 @@ // Create the tables self::init_tables($my_wpdb); + // Ensure country/dt index exists for performance + $has_index = $my_wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = 'idx_country_dt'", $GLOBALS['wpdb']->prefix)); + if (!$has_index || 0 === count($has_index)) { + $my_wpdb->query(sprintf('CREATE INDEX idx_country_dt ON %sslim_stats (country, dt)', $GLOBALS['wpdb']->prefix)); + } + update_option('slimstat_country_dt_indexed', 'yes'); + + // --- Add (dt, screen_width, screen_height) index for Top Screen Resolutions --- + $dt_screen_index = $my_wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = 'idx_dt_screen_width_screen_height'", $GLOBALS['wpdb']->prefix)); + if (empty($dt_screen_index)) { + $my_wpdb->query(sprintf('CREATE INDEX idx_dt_screen_width_screen_height ON %sslim_stats (dt, screen_width, screen_height)', $GLOBALS['wpdb']->prefix)); + } + update_option('slimstat_dt_screen_indexed', 'yes'); + + // --- Add (dt, browser, browser_version) index for Top Browsers --- + $dt_browser_index = $my_wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = 'idx_dt_browser_browser_version'", $GLOBALS['wpdb']->prefix)); + if (empty($dt_browser_index)) { + $my_wpdb->query(sprintf('CREATE INDEX idx_dt_browser_browser_version ON %sslim_stats (dt, browser, browser_version)', $GLOBALS['wpdb']->prefix)); + } + update_option('slimstat_dt_browser_indexed', 'yes'); + + // --- Add (dt, platform) index for Top Platforms --- + $dt_platform_index = $my_wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = 'idx_dt_platform'", $GLOBALS['wpdb']->prefix)); + if (empty($dt_platform_index)) { + $my_wpdb->query(sprintf('CREATE INDEX idx_dt_platform ON %sslim_stats (dt, platform)', $GLOBALS['wpdb']->prefix)); + } + update_option('slimstat_dt_platform_indexed', 'yes'); + return true; } @@ -378,10 +530,10 @@ // Table that stores the actual data about visits $stats_table_sql = " - CREATE TABLE IF NOT EXISTS {$GLOBALS['wpdb']->prefix}slim_stats ( - id INT UNSIGNED NOT NULL auto_increment, - ip VARCHAR(39) DEFAULT NULL, - other_ip VARCHAR(39) DEFAULT NULL, + CREATE TABLE IF NOT EXISTS {$GLOBALS['wpdb']->prefix}slim_stats ( + id INT UNSIGNED NOT NULL auto_increment, + ip VARCHAR(39) DEFAULT NULL, + other_ip VARCHAR(39) DEFAULT NULL, username VARCHAR(256) DEFAULT NULL, email VARCHAR(256) DEFAULT NULL, @@ -421,7 +573,7 @@ dt INT(10) UNSIGNED DEFAULT 0, CONSTRAINT PRIMARY KEY (id), - INDEX {$GLOBALS['wpdb']->prefix}slim_stats_dt_idx (dt), + INDEX {$GLOBALS['wpdb']->prefix}slim_stats_dt_idx (dt), INDEX {$GLOBALS['wpdb']->prefix}stats_resource_idx( resource( 20 ) ), INDEX {$GLOBALS['wpdb']->prefix}stats_browser_idx( browser( 10 ) ), INDEX {$GLOBALS['wpdb']->prefix}stats_searchterms_idx( searchterms( 15 ) ), @@ -472,6 +624,23 @@ if (empty(wp_slimstat::$settings['version'])) { wp_slimstat::$settings['version'] = SLIMSTAT_ANALYTICS_VERSION; } + + $index_defs = [ + ['name' => 'idx_country_dt', 'sql' => sprintf('CREATE INDEX idx_country_dt ON %sslim_stats (country, dt)', $GLOBALS['wpdb']->prefix), 'option' => 'slimstat_country_dt_indexed'], + ['name' => 'idx_dt_screen_width_screen_height', 'sql' => sprintf('CREATE INDEX idx_dt_screen_width_screen_height ON %sslim_stats (dt, screen_width, screen_height)', $GLOBALS['wpdb']->prefix), 'option' => 'slimstat_dt_screen_indexed'], + ['name' => 'idx_dt_browser_browser_version', 'sql' => sprintf('CREATE INDEX idx_dt_browser_browser_version ON %sslim_stats (dt, browser, browser_version)', $GLOBALS['wpdb']->prefix), 'option' => 'slimstat_dt_browser_indexed'], + ['name' => 'idx_dt_platform', 'sql' => sprintf('CREATE INDEX idx_dt_platform ON %sslim_stats (dt, platform)', $GLOBALS['wpdb']->prefix), 'option' => 'slimstat_dt_platform_indexed'], + // Speeds up "Currently Online" queries using dt_out > NOW()-300 + ['name' => 'idx_dt_out', 'sql' => sprintf('CREATE INDEX idx_dt_out ON %sslim_stats (dt_out)', $GLOBALS['wpdb']->prefix), 'option' => 'slimstat_dt_out_indexed'], + ]; + foreach ($index_defs as $idx) { + $exists = $_wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = '%s'", $GLOBALS['wpdb']->prefix, $idx['name'])); + if (empty($exists)) { + $_wpdb->query($idx['sql']); + } + update_option($idx['option'], 'yes'); + } + } // END: init_tables @@ -504,18 +673,23 @@ unset(wp_slimstat::$settings['no_maxmind_warning']); unset(wp_slimstat::$settings['no_browscap_warning']); unset(wp_slimstat::$settings['use_european_separators']); - unset(wp_slimstat::$settings['date_format']); - unset(wp_slimstat::$settings['time_format']); - unset(wp_slimstat::$settings['expand_details']); - - // Add table indexes for improved performance - $check_index = wp_slimstat::$wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = '%sstats_resource_idx'", $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)); - if (empty($check_index)) { - wp_slimstat::$wpdb->query(sprintf('ALTER TABLE %sslim_stats ADD INDEX %sstats_resource_idx( resource( 20 ) )', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)); - wp_slimstat::$wpdb->query(sprintf('ALTER TABLE %sslim_stats ADD INDEX %sstats_browser_idx( browser( 10 ) )', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)); - wp_slimstat::$wpdb->query(sprintf('ALTER TABLE %sslim_stats ADD INDEX %sstats_searchterms_idx( searchterms( 15 ) )', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)); + unset($wp_slimstat::$settings['date_format']); + unset($wp_slimstat::$settings['time_format']); + unset($wp_slimstat::$settings['expand_details']); + + // Add table indexes for improved performance (idempotent) + $indexes = [ + ['name' => $GLOBALS['wpdb']->prefix . 'stats_resource_idx', 'sql' => sprintf('ALTER TABLE %sslim_stats ADD INDEX %sstats_resource_idx( resource( 20 ) )', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)], + ['name' => $GLOBALS['wpdb']->prefix . 'stats_browser_idx', 'sql' => sprintf('ALTER TABLE %sslim_stats ADD INDEX %sstats_browser_idx( browser( 10 ) )', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)], + ['name' => $GLOBALS['wpdb']->prefix . 'stats_searchterms_idx', 'sql' => sprintf('ALTER TABLE %sslim_stats ADD INDEX %sstats_searchterms_idx( searchterms( 15 ) )', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)], + ['name' => $GLOBALS['wpdb']->prefix . 'stats_fingerprint_idx', 'sql' => sprintf('ALTER TABLE %sslim_stats ADD INDEX %sstats_fingerprint_idx( fingerprint( 20 ) )', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix)], + ]; + foreach ($indexes as $index) { + $check_index = wp_slimstat::$wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = '%s'", $GLOBALS['wpdb']->prefix, $index['name'])); + if (empty($check_index)) { + wp_slimstat::$wpdb->query($index['sql']); + } } - wp_slimstat::$settings['db_indexes'] = 'on'; } @@ -545,6 +719,19 @@ $my_wpdb->query(sprintf("UPDATE %sslim_stats SET notes = CONCAT( '[', REPLACE( notes, ';', '][' ), ']' ) WHERE notes NOT LIKE '[%%'", $GLOBALS['wpdb']->prefix)); } + // --- Updates for version 5.4.0 --- + if (version_compare(wp_slimstat::$settings['version'], '5.4.0', '<')) { + // Migrate legacy 'adblock' tracking method to 'adblock_bypass' (renamed in v5.3.0) + if (!empty(wp_slimstat::$settings['tracking_request_method']) && 'adblock' === wp_slimstat::$settings['tracking_request_method']) { + wp_slimstat::$settings['tracking_request_method'] = 'adblock_bypass'; + } + + // Default use_separate_menu to 'on' if not already set + if (empty(wp_slimstat::$settings['use_separate_menu'])) { + wp_slimstat::$settings['use_separate_menu'] = 'on'; + } + } + // Now we can update the version stored in the database wp_slimstat::$settings['version'] = SLIMSTAT_ANALYTICS_VERSION; wp_slimstat::$settings['notice_latest_news'] = 'on'; @@ -567,6 +754,9 @@ return; } + // Initialize the new Reports system FIRST before legacy system loads + \SlimStat\Reports\Bootstrap::get_instance()->init(); + // The Reports library is only loaded on the plugin's screens include_once(plugin_dir_path(__FILE__) . 'view/wp-slimstat-reports.php'); wp_slimstat_reports::init(); @@ -576,7 +766,6 @@ if (empty(wp_slimstat_reports::$reports[$a_report_id])) { continue; } - wp_add_dashboard_widget($a_report_id, wp_slimstat_reports::$reports[$a_report_id]['title'], ['wp_slimstat_reports', 'callback_wrapper']); } } @@ -607,7 +796,15 @@ public static function wp_slimstat_stylesheet($_hook = '') { wp_register_style('wp-slimstat', plugins_url('/admin/assets/css/admin.css', __DIR__), false, SLIMSTAT_ANALYTICS_VERSION); - wp_enqueue_style('wp-slimstat', [], [], SLIMSTAT_ANALYTICS_VERSION, 'all'); + wp_enqueue_style('wp-slimstat'); + + wp_register_style( + 'wp-slimstat-header-modern', + plugins_url('/admin/assets/css/header-modern.css', __DIR__), + ['wp-slimstat'], + SLIMSTAT_ANALYTICS_VERSION + ); + wp_enqueue_style('wp-slimstat-header-modern'); if (!empty(wp_slimstat::$settings['custom_css'])) { wp_add_inline_style('wp-slimstat', wp_slimstat::$settings['custom_css']); @@ -631,22 +828,79 @@ */ public static function wp_slimstat_enqueue_scripts($_hook = '') { - wp_enqueue_script('dashboard'); - wp_enqueue_script('jquery-ui-datepicker'); + $current_screen = get_current_screen(); + if ($current_screen && str_contains($current_screen->id ?? '', 'slim')) { + wp_enqueue_script('dashboard'); + wp_enqueue_script('jquery-ui-datepicker'); + wp_enqueue_script('jquery-ui-sortable'); + } // Enqueue the built-in code editor to use on the Settings - if (self::$current_screen) { + if ($current_screen) { wp_enqueue_code_editor(['type' => 'text/html']); } - wp_enqueue_script('slimstat_admin', plugins_url('/admin/assets/js/admin.js', __DIR__), ['jquery-ui-dialog'], SLIMSTAT_ANALYTICS_VERSION, false); + // Enqueue date range picker assets for report pages + $should_load_datepicker = false; + if (isset($_GET['page'])) { + $page = sanitize_text_field($_GET['page']); + if (str_contains($page, 'slim') && !str_contains($page, 'setting')) { + $should_load_datepicker = true; + } + } + + if ($should_load_datepicker) { + + // Enqueue moment.js + wp_enqueue_script('slimstat-moment', plugins_url('/admin/assets/js/daterangepicker/moment.min.js', __DIR__), [], '2.30.2', true); + + // Enqueue daterangepicker + wp_enqueue_script('slimstat-daterangepicker', plugins_url('/admin/assets/js/daterangepicker/daterangepicker.min.js', __DIR__), ['jquery', 'slimstat-moment'], '3.1.0', true); + + // Enqueue our custom date picker + wp_enqueue_script('slimstat-custom-datepicker', plugins_url('/admin/assets/js/daterangepicker/slimstat-daterangepicker.js', __DIR__), ['jquery', 'slimstat-daterangepicker'], SLIMSTAT_ANALYTICS_VERSION, true); + + // Enqueue date picker styles + wp_enqueue_style('slimstat-daterangepicker-base', plugins_url('/admin/assets/css/daterangepicker/daterangepicker.css', __DIR__), [], '3.1.0'); + wp_enqueue_style('slimstat-daterangepicker-custom', plugins_url('/admin/assets/css/daterangepicker/slimstat-datepicker-styles.css', __DIR__), ['slimstat-daterangepicker-base'], SLIMSTAT_ANALYTICS_VERSION); + + // Localize date picker script + $datepicker_params = [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'clear_cache_nonce' => wp_create_nonce('slimstat_clear_cache'), + 'options' => [ + 'wp_timezone' => DateRangeHelper::get_wp_timezone(), + 'start_of_week' => DateRangeHelper::get_week_start(), + 'date_format' => DateRangeHelper::get_date_format() + ], + 'strings' => DateRangeHelper::get_localized_strings() + ]; + wp_localize_script('slimstat-custom-datepicker', 'SlimStatDatePicker', $datepicker_params); + } + + wp_enqueue_script('slimstat_admin', plugins_url('/admin/assets/js/admin.js', __DIR__), ['jquery-ui-dialog'], SLIMSTAT_ANALYTICS_VERSION, true); + + // Enqueue notification assets if notifications are enabled + if (wp_slimstat::$settings['display_notifications'] == 'on') { + wp_enqueue_style('slimstat_notifications', plugins_url('/admin/assets/css/notifications.css', __DIR__), [], SLIMSTAT_ANALYTICS_VERSION); + wp_enqueue_style('slimstat_header_notifications', plugins_url('/admin/assets/css/header-notifications.css', __DIR__), [], SLIMSTAT_ANALYTICS_VERSION); + wp_enqueue_script('slimstat_notifications', plugins_url('/admin/assets/js/notifications.js', __DIR__), ['jquery'], SLIMSTAT_ANALYTICS_VERSION, false); + + // Pass notification data to Javascript + $notification_params = [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'nonce' => wp_create_nonce('wp_rest'), + ]; + wp_localize_script('slimstat_notifications', 'slimstat_admin', $notification_params); + } // Pass some information to Javascript $params = [ - 'async_load' => empty(wp_slimstat::$settings['async_load']) ? 'no' : wp_slimstat::$settings['async_load'], - 'datepicker_image' => plugins_url('/admin/assets/images/datepicker.png', __DIR__), - 'refresh_interval' => intval(wp_slimstat::$settings['refresh_interval']), - 'page_location' => self::$page_location, + 'async_load' => empty(wp_slimstat::$settings['async_load']) ? 'no' : wp_slimstat::$settings['async_load'], + 'datepicker_image' => plugins_url('/admin/assets/images/datepicker.png', __DIR__), + 'refresh_interval' => intval(wp_slimstat::$settings['refresh_interval']), + 'page_location' => self::$page_location, + 'clear_cache_nonce' => wp_create_nonce('slimstat_clear_cache'), ]; wp_localize_script('slimstat_admin', 'SlimStatAdminParams', $params); } @@ -669,28 +923,50 @@ } // Find the first available location (screens with no reports assigned to them are hidden from the nav) - $parent = 'slimview1'; - if (is_array(self::$meta_user_reports)) { - $parent = ''; - foreach (self::$screens_info as $a_screen_id => $a_screen_info) { - if (!empty(self::$meta_user_reports[$a_screen_id]) && $a_screen_info['show_in_sidebar']) { - $parent = $a_screen_id; - break; - } - } - - if (empty($parent)) { - $parent = 'slimlayout'; + $parent = ''; + if (is_array(self::$meta_user_reports)) { + foreach (self::$screens_info as $a_screen_id => $a_screen_info) { + if (!empty(self::$meta_user_reports[$a_screen_id]) && $a_screen_info['show_in_sidebar']) { + $parent = $a_screen_id; + break; + } + } + } + + // If no parent was found in the user meta, use the first available screen as the parent + if (empty($parent) && !empty(self::$screens_info)) { + $parent = array_key_first(self::$screens_info); + } + + // Don't show the menu if no screens are available at all + if (empty($parent) || !isset(self::$screens_info[$parent])) { + return null; + } + + self::$main_menu_slug = $parent; + + // Build menu title with notification badge + $menu_title = __('SlimStat', 'wp-slimstat'); + if (class_exists(NotificationFactory::class) && wp_slimstat::$settings['display_notifications'] === 'on') { + $notification_count = NotificationFactory::getNewNotificationCount(); + if ($notification_count > 0) { + $menu_title .= sprintf( + ' <span class="update-plugins count-%d"><span class="plugin-count">%s</span></span>', + $notification_count, + number_format_i18n($notification_count) + ); } } - // Get the current menu position - $new_entry = []; - if ('no' == wp_slimstat::$settings['use_separate_menu'] || is_network_admin()) { - $new_entry[] = add_menu_page(__('Slimstat', 'wp-slimstat'), __('Slimstat', 'wp-slimstat'), $minimum_capability, $parent, [self::class, 'wp_slimstat_include_view'], 'dashicons-chart-area'); - } else { - $parent = 'admin.php'; - } + // Add the main menu + add_menu_page( + __('SlimStat', 'wp-slimstat'), + $menu_title, + $minimum_capability, + $parent, + [self::class, 'wp_slimstat_include_view'], + 'dashicons-chart-area' + ); foreach (self::$screens_info as $a_screen_id => $a_screen_info) { if (isset(self::$meta_user_reports[$a_screen_id]) && empty(self::$meta_user_reports[$a_screen_id])) { @@ -735,7 +1011,41 @@ // END: add_menus /** - * Adds a new entry in the WordPress Admin Bar + * Enqueue admin bar modal styles globally (admin + frontend) + */ + public static function enqueue_adminbar_styles() + { + if (is_admin_bar_showing()) { + wp_enqueue_style( + 'slimstat-adminbar', + plugins_url('/admin/assets/css/admin-bar-modal.css', __DIR__), + [], + SLIMSTAT_ANALYTICS_VERSION + ); + + // Enqueue admin bar realtime JS for online visitors update (frontend only) + // In admin, admin.js handles this via slimstat:minute_pulse + if (!is_admin()) { + wp_enqueue_script( + 'slimstat-adminbar-realtime', + plugins_url('/admin/assets/js/adminbar-realtime.js', __DIR__), + [], + SLIMSTAT_ANALYTICS_VERSION, + true + ); + + wp_localize_script('slimstat-adminbar-realtime', 'SlimStatAdminBar', [ + 'ajax_url' => admin_url('admin-ajax.php'), + 'security' => wp_create_nonce('meta-box-order'), + ]); + } + } + } + + // END: enqueue_adminbar_styles + + /** + * Adds a new entry in the WordPress Admin Bar with stats modal */ public static function add_menu_to_adminbar() { @@ -747,51 +1057,247 @@ $minimum_capability = wp_slimstat::$settings['capability_can_view']; } - // Find the first available location (screens with no reports assigned to them are hidden from the nav) - $parent = 'slimview1'; - if (is_array(self::$meta_user_reports)) { - $parent = ''; - foreach (self::$screens_info as $a_screen_id => $a_screen_info) { - if (!empty(self::$meta_user_reports[$a_screen_id]) && $a_screen_info['show_in_sidebar']) { - $parent = $a_screen_id; - break; - } - } - - if (empty($parent)) { - $parent = 'slimlayout'; - } + if (!current_user_can($minimum_capability)) { + return; } - if (current_user_can($minimum_capability)) { - $view_url = get_admin_url($GLOBALS['blog_id'], 'admin.php?page='); - - $GLOBALS['wp_admin_bar']->add_menu([ - 'id' => 'slimstat-header', - 'title' => '<span class="ab-icon dashicons dashicons-chart-area" style="font-size:1rem;margin-top:3px"></span>' . __('Slimstat', 'wp-slimstat'), - 'href' => $view_url . $parent, - ]); - - foreach (self::$screens_info as $a_screen_id => $a_screen_info) { - if (isset(self::$meta_user_reports[$a_screen_id]) && empty(self::$meta_user_reports[$a_screen_id])) { - continue; - } - - $minimum_capability = 'read'; - if (!empty($a_screen_info['capability']) && false === strpos(wp_slimstat::$settings[$a_screen_info['capability']], (string) $GLOBALS['current_user']->user_login) && !empty(wp_slimstat::$settings['capability_' . $a_screen_info['capability']])) { - $minimum_capability = wp_slimstat::$settings['capability_' . $a_screen_info['capability']]; - } - - if ($a_screen_info['show_in_sidebar'] && current_user_can($minimum_capability)) { - $GLOBALS['wp_admin_bar']->add_menu([ - 'id' => $a_screen_id, - 'href' => $view_url . $a_screen_id, - 'parent' => 'slimstat-header', - 'title' => $a_screen_info['title'], - ]); + global $wpdb; + $table = "{$wpdb->prefix}slim_stats"; + $today_start = mktime(0, 0, 0); + $yesterday_start = $today_start - 86400; + $yesterday_end = $today_start - 1; + + // Visitors Today (unique IPs) + $visitors_today = (int) $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(DISTINCT ip) FROM {$table} WHERE dt >= %d", + $today_start + )); + + // Views Today (pageviews) + $views_today = (int) $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(id) FROM {$table} WHERE dt >= %d", + $today_start + )); + + // Yesterday's visitors + $visitors_yesterday = (int) $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(DISTINCT ip) FROM {$table} WHERE dt BETWEEN %d AND %d", + $yesterday_start, $yesterday_end + )); + + // Yesterday's views + $views_yesterday = (int) $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(id) FROM {$table} WHERE dt BETWEEN %d AND %d", + $yesterday_start, $yesterday_end + )); + + // Referrals Today (external referrers only) + $site_host = parse_url(home_url(), PHP_URL_HOST); + $referrals_today = (int) $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(id) FROM {$table} WHERE dt >= %d AND referer IS NOT NULL AND referer NOT LIKE %s", + $today_start, '%' . $wpdb->esc_like($site_host) . '%' + )); + + // Referrals Yesterday + $referrals_yesterday = (int) $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(id) FROM {$table} WHERE dt BETWEEN %d AND %d AND referer IS NOT NULL AND referer NOT LIKE %s", + $yesterday_start, $yesterday_end, '%' . $wpdb->esc_like($site_host) . '%' + )); + + // Online Users — same 30-minute window query as header.php + $current_minute_start = (int) floor(current_time('timestamp') / 60) * 60; + $window_minutes = 30; + $window_start = $current_minute_start - (($window_minutes - 1) * 60); + + $online_count = (int) $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) FROM ( + SELECT visit_id, MAX( + CASE + WHEN dt_out IS NOT NULL AND dt_out > 0 AND dt_out >= dt THEN dt_out + ELSE dt + END + ) AS last_activity + FROM {$table} + WHERE visit_id > 0 + AND (dt >= %d OR (dt_out IS NOT NULL AND dt_out >= %d)) + GROUP BY visit_id + HAVING (FLOOR(last_activity / 60) * 60 + 59) >= %d + ) live_sessions", + $window_start, $window_start, $window_start + )); + $online_count = max(0, $online_count); + + // Determine premium status early (needed for chart data) + $is_pro = wp_slimstat::pro_is_installed(); + + // Query minute-by-minute data for the CSS bar chart (30-minute window) + // Only fetch real data for Pro users + if ($is_pro) { + $minute_data_raw = $wpdb->get_results($wpdb->prepare( + "SELECT + FLOOR(dt / 60) * 60 AS minute_bucket, + COUNT(DISTINCT visit_id) AS visitor_count + FROM {$table} + WHERE dt >= %d AND dt <= %d AND visit_id > 0 + GROUP BY minute_bucket + ORDER BY minute_bucket ASC", + $window_start, $current_minute_start + 59 + ), ARRAY_A); + + // Build minute data array (30 slots) + $minute_data = array_fill(0, 30, 0); + foreach ($minute_data_raw as $row) { + $bucket = (int) $row['minute_bucket']; + $index = (int) (($bucket - $window_start) / 60); + if ($index >= 0 && $index < 30) { + $minute_data[$index] = (int) $row['visitor_count']; } } + $max_count = max(1, max($minute_data)); + } else { + // Fake placeholder data for non-Pro users + $minute_data = [3, 5, 4, 7, 6, 8, 5, 9, 7, 6, 8, 10, 7, 5, 6, 8, 9, 7, 6, 5, 8, 10, 9, 7, 6, 8, 5, 7, 6, 8]; + $max_count = 10; + } + + // Build chart HTML + $chart_bars = ''; + $total_bars = count($minute_data); + foreach ($minute_data as $i => $count) { + $height_pct = round(($count / $max_count) * 100); + $is_peak = ($count === $max_count && $count > 0); + $bar_class = $is_peak ? ' slimstat-adminbar__chart-bar--peak' : ''; + $minutes_ago = $total_bars - 1 - $i; // 29 for first bar, 0 for last bar + $time_text = $minutes_ago === 0 + ? esc_html__('Now', 'wp-slimstat') + : sprintf('%d %s', $minutes_ago, esc_html__('min ago', 'wp-slimstat')); + $chart_bars .= sprintf( + '<div class="slimstat-adminbar__chart-bar%s" style="height:%d%%" data-count="%d" data-minutes-ago="%d">' + . '<span class="slimstat-adminbar__chart-tooltip">' + . '<strong>%s</strong>' + . '%s: %d<br>' + . '%s' + . '</span></div>', + $bar_class, + max($height_pct, 3), // minimum 3% for visibility + $count, + $minutes_ago, + esc_html__('Online Users', 'wp-slimstat'), + esc_html__('Count', 'wp-slimstat'), + $count, + $time_text + ); + } + $view_url = get_admin_url($GLOBALS['blog_id'], 'admin.php?page='); + $overview_url = $view_url . 'slimview2'; + $upgrade_url = 'https://wp-slimstat.com/pricing/?utm_source=wp-slimstat&utm_medium=link&utm_campaign=adminbar'; + + // Add parent node + $GLOBALS['wp_admin_bar']->add_menu([ + 'id' => 'slimstat-header', + 'title' => '<span class="ab-icon dashicons dashicons-chart-area" style="font-size:1rem;margin-top:3px"></span>' + . sprintf(__('Online: %s', 'wp-slimstat'), '<span id="slimstat-adminbar-online-header">' . number_format_i18n($online_count) . '</span>'), + 'href' => $overview_url, + ]); + + // Add stats grid node + // For non-Pro users, show fake data for Views and Referrals + $views_display = $is_pro ? number_format_i18n($views_today) : '248'; + $views_yesterday_display = $is_pro ? number_format_i18n($views_yesterday) : '312'; + $referrals_display = $is_pro ? number_format_i18n($referrals_today) : '18'; + $referrals_yesterday_display = $is_pro ? number_format_i18n($referrals_yesterday) : '24'; + $blur_class = $is_pro ? '' : ' slimstat-adminbar__stat-card--blur'; + + $stats_html = '<div class="slimstat-adminbar__stats-grid">' + // Online Users (top left) + . '<div class="slimstat-adminbar__stat-card">' + . '<div class="slimstat-adminbar__stat-title">' . esc_html__('Online Users', 'wp-slimstat') + . ' <span class="slimstat-adminbar__realtime-dot"></span></div>' + . '<div class="slimstat-adminbar__stat-count" id="slimstat-adminbar-online-count">' . number_format_i18n($online_count) . '</div>' + . '<div class="slimstat-adminbar__realtime-badge">' + . '<span class="slimstat-adminbar__realtime-pulse"></span> ' + . esc_html__('Realtime', 'wp-slimstat') . '</div>' + . '</div>' + // Visitors Today (top right) + . '<div class="slimstat-adminbar__stat-card">' + . '<div class="slimstat-adminbar__stat-title">' . esc_html__('Visitors Today', 'wp-slimstat') . '</div>' + . '<div class="slimstat-adminbar__stat-count">' . number_format_i18n($visitors_today) . '</div>' + . '<div class="slimstat-adminbar__stat-comparison">' + . sprintf(esc_html__('was %s last day', 'wp-slimstat'), number_format_i18n($visitors_yesterday)) + . '</div></div>' + // Views Today (bottom left) - blur for non-Pro + . '<div class="slimstat-adminbar__stat-card' . $blur_class . '">' + . '<div class="slimstat-adminbar__stat-title">' . esc_html__('Views Today', 'wp-slimstat') . '</div>' + . '<div class="slimstat-adminbar__stat-count">' . $views_display . '</div>' + . '<div class="slimstat-adminbar__stat-comparison">' + . sprintf(esc_html__('was %s last day', 'wp-slimstat'), $views_yesterday_display) + . '</div></div>' + // Referrals Today (bottom right) - blur for non-Pro + . '<div class="slimstat-adminbar__stat-card' . $blur_class . '">' + . '<div class="slimstat-adminbar__stat-title">' . esc_html__('Referrals Today', 'wp-slimstat') . '</div>' + . '<div class="slimstat-adminbar__stat-count">' . $referrals_display . '</div>' + . '<div class="slimstat-adminbar__stat-comparison">' + . sprintf(esc_html__('was %s last day', 'wp-slimstat'), $referrals_yesterday_display) + . '</div></div>' + . '</div>'; + + $GLOBALS['wp_admin_bar']->add_node([ + 'id' => 'slimstat-adminbar-stats', + 'parent' => 'slimstat-header', + 'title' => $stats_html, + 'meta' => ['class' => 'slimstat-adminbar__stats-wrapper'], + ]); + + // Add chart node + $chart_wrapper_class = $is_pro ? 'slimstat-adminbar__chart-container' : 'slimstat-adminbar__chart-container slimstat-adminbar__chart-blur'; + $chart_html = '<div class="' . $chart_wrapper_class . '">' + . '<div class="slimstat-adminbar__chart-bars">' . $chart_bars . '</div>' + . '</div>'; + + $GLOBALS['wp_admin_bar']->add_node([ + 'id' => 'slimstat-adminbar-chart', + 'parent' => 'slimstat-header', + 'title' => $chart_html, + 'meta' => ['class' => 'slimstat-adminbar__chart-wrapper'], + ]); + + // Add CTA node (free users only) + if (!$is_pro) { + $cta_html = '<div class="slimstat-adminbar__cta">' + . '<div class="slimstat-adminbar__cta-text">' + . esc_html__('Unlock the Full Power of SlimStat Analytics', 'wp-slimstat') + . '</div>' + . '<a href="' . esc_url($upgrade_url) . '" target="_blank" class="slimstat-adminbar__cta-button">' + . esc_html__('Unlock SlimStat Pro', 'wp-slimstat') . '</a>' + . '</div>'; + + $GLOBALS['wp_admin_bar']->add_node([ + 'id' => 'slimstat-adminbar-cta', + 'parent' => 'slimstat-header', + 'title' => $cta_html, + 'meta' => ['class' => 'slimstat-adminbar__cta-wrapper'], + ]); } + + // Add footer node + $footer_html = '<div class="slimstat-adminbar__footer">' + . '<div class="slimstat-adminbar__footer-logo">' + . '<svg width="20" height="20" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">' + . '<path fill-rule="evenodd" clip-rule="evenodd" d="M0 15C0 6.71582 6.7069 0 14.9801 0C20.2546 0 24.8865 2.72788 27.5572 6.84316L19.371 15.1743H19.3643V15.1877C19.0765 15.4893 18.5946 15.496 18.2934 15.2011C18.2599 15.1743 18.2331 15.1408 18.2064 15.1005L15.9239 11.9638C13.9627 9.27614 10.047 9.03485 7.77787 11.4678L0.589029 19.1756C0.194112 17.8217 0 16.4142 0 15ZM2.69079 23.5858C5.40167 27.4665 9.89302 30.0067 14.9801 30.0067C23.2533 30.0067 29.9602 23.2909 29.9602 15.0067C29.9602 13.7399 29.8062 12.5134 29.5117 11.3405L22.604 18.3646C20.3148 20.7172 16.466 20.4424 14.5316 17.7949L12.2491 14.6582C12.0015 14.3231 11.5329 14.2426 11.1916 14.4906C11.1514 14.5174 11.1179 14.5509 11.0845 14.5845L2.69079 23.5858Z" fill="#F22F46"/>' + . '</svg>' + . '<span class="slimstat-adminbar__footer-brand">SlimStat</span>' + . '</div>' + . '<a href="' . esc_url($overview_url) . '" class="slimstat-adminbar__footer-link">' + . esc_html__('Explore Details', 'wp-slimstat') + . ' <span class="dashicons dashicons-external" style="font-size:12px"></span>' + . '</a></div>'; + + $GLOBALS['wp_admin_bar']->add_node([ + 'id' => 'slimstat-adminbar-footer', + 'parent' => 'slimstat-header', + 'title' => $footer_html, + 'meta' => ['class' => 'slimstat-adminbar__footer-wrapper'], + ]); } // END: add_menu_to_adminbar @@ -814,7 +1320,17 @@ include(__DIR__ . '/view/layout.php'); } - // END: wp_slimstat_include_addons + // END: wp_slimstat_include_layout + + /** + * Includes the email report screen + */ + public static function wp_slimstat_include_email_report() + { + include(__DIR__ . '/view/email-report.php'); + } + + // END: wp_slimstat_include_email_report /** * Handles the upgrade to pro from the free version @@ -882,7 +1398,7 @@ } } } - + return null; } @@ -894,7 +1410,7 @@ public static function add_column_header($_columns = []) { if (0 == wp_slimstat::$settings['posts_column_day_interval']) { - wp_slimstat::$settings['posts_column_day_interval'] = 30; + wp_slimstat::$settings['posts_column_day_interval'] = 28; } if ('on' == wp_slimstat::$settings['posts_column_pageviews']) { @@ -941,7 +1457,7 @@ } else { echo '<div class="notice notice-' . esc_attr($_type) . ' slimstat-notice">' . $_message . '</div>'; } - + return null; } @@ -957,27 +1473,6 @@ // END: show_latest_news - /** - * Displays a message if this user speaks a language other than English, to encourage them to help us translate Slimstat in their language - */ - public static function show_translate_notice() - { - // echo '<div class="notice slimstat-notice" style="padding:10px"><span>'.self::$admin_notice.'</span></div>'; - include_once(plugin_dir_path(__FILE__) . '../languages/i18n-v3.php'); - include_once(plugin_dir_path(__FILE__) . '../languages/i18n-wordpressorg-v3.php'); - - $i18n_module = new Yoast_I18n_WordPressOrg_v3( - [ - 'textdomain' => 'wp-slimstat', - 'plugin_name' => 'Slimstat Analytics', - ], - false - ); - - self::show_message($i18n_module->get_promo_message(), 'warning', 'translate'); - } - - // END: show_translate_notice /** * Handles the Ajax request to hide the admin notice @@ -1012,7 +1507,6 @@ if (!$current_user_can_delete || !wp_verify_nonce($_POST['security'], 'meta-box-order')) { return; } - $my_wpdb->query(sprintf('DELETE ts FROM %sslim_stats ts WHERE ts.id = %d', $GLOBALS['wpdb']->prefix, $pageview_id)); exit(); } @@ -1064,12 +1558,15 @@ return; } + // Initialize the new Reports system FIRST before legacy system loads + \SlimStat\Reports\Bootstrap::get_instance()->init(); + include_once(plugin_dir_path(__FILE__) . 'view/wp-slimstat-reports.php'); wp_slimstat_reports::init(); $saved_filters = get_option('slimstat_filters', []); - switch ($_POST['type']) { + switch (sanitize_key(wp_unslash($_POST['type'] ?? ''))) { case 'save': $new_filter = json_decode(stripslashes_deep(sanitize_text_field($_POST['filter_array'])), true); @@ -1130,34 +1627,439 @@ // END: manage_filters - public static function update_geoip_database() + /** + * AJAX handler to get current online visitors count + */ + public static function get_online_visitors() { - check_ajax_referer('wp_rest', 'security'); + check_ajax_referer('meta-box-order', 'security'); + + // If this user is whitelisted, we use the minimum capability + $minimum_capability = 'read'; + if (false === strpos(wp_slimstat::$settings['can_view'], (string) $GLOBALS['current_user']->user_login) && !empty(wp_slimstat::$settings['capability_can_view'])) { + $minimum_capability = wp_slimstat::$settings['capability_can_view']; + } + + if (!current_user_can($minimum_capability)) { + wp_send_json_error(['message' => 'Insufficient permissions']); + return; + } - try { - $geographicProvider = new GeoService(); + global $wpdb; + $table = "{$wpdb->prefix}slim_stats"; + $current_minute_start = (int) floor(current_time('timestamp') / 60) * 60; + $window_minutes = 30; // 30 minutes - synced with Live Analytics Users Live + $window_start = $current_minute_start - (($window_minutes - 1) * 60); + + $sql = $wpdb->prepare( + " + SELECT COUNT(*) FROM ( + SELECT visit_id, MAX( + CASE + WHEN dt_out IS NOT NULL AND dt_out > 0 AND dt_out >= dt THEN dt_out + ELSE dt + END + ) AS last_activity + FROM {$table} + WHERE visit_id > 0 + AND ( + dt >= %d + OR ( dt_out IS NOT NULL AND dt_out >= %d ) + ) + GROUP BY visit_id + HAVING (FLOOR(last_activity / 60) * 60 + 59) >= %d + ) live_sessions + ", + $window_start, + $window_start, + $window_start + ); - $result = $geographicProvider - ->setUpdate(true) - ->setEnableMaxmind(\wp_slimstat::$settings['enable_maxmind']) - ->setMaxmindLicense(\wp_slimstat::$settings['maxmind_license_key']) - ->download(); + $online_visitors = (int) $wpdb->get_var($sql); + $online_visitors = max(0, (int) $online_visitors); - wp_send_json_success($result['notice']); - } catch (\Exception $exception) { + wp_send_json_success([ + 'count' => $online_visitors, + 'formatted' => number_format_i18n($online_visitors) + ]); + } - wp_send_json_error($exception->getMessage()); + // END: get_online_visitors + + /** + * Helper function to get icon URL for filter options + */ + private static function get_filter_icon_url($dimension, $value) + { + $icon_url = ''; + + switch ($dimension) { + case 'country': + // Country flags are SVG files named by country code (lowercase) + $country_code = strtolower($value); + $flag_rel = '/admin/assets/images/flags/' . $country_code . '.svg'; + $flag_path = SLIMSTAT_ANALYTICS_DIR . $flag_rel; + if (is_readable($flag_path)) { + $icon_url = SLIMSTAT_ANALYTICS_URL . $flag_rel; + } + break; + + case 'browser': + // Browser icons are PNG files named by browser name (lowercase) + $browser_name = strtolower($value); + $browser_rel = '/admin/assets/images/browsers/' . $browser_name . '.png'; + $browser_path = SLIMSTAT_ANALYTICS_DIR . $browser_rel; + if (is_readable($browser_path)) { + $icon_url = SLIMSTAT_ANALYTICS_URL . $browser_rel; + } + break; + + case 'language': + // Language flags use the last part of the language code (e.g., en-US -> us) + $language_parts = explode('-', $value); + $last_part = strtolower(end($language_parts)); + $flag_rel = '/admin/assets/images/flags/' . $last_part . '.svg'; + $flag_path = SLIMSTAT_ANALYTICS_DIR . $flag_rel; + if (is_readable($flag_path)) { + $icon_url = SLIMSTAT_ANALYTICS_URL . $flag_rel; + } + break; + + case 'platform': + // Platform/OS icons are WEBP files with abbreviated names + $os_map = [ + 'win' => 'win', + 'windows' => 'win', + 'mac' => 'mac', + 'macosx' => 'mac', + 'linux' => 'lin', + 'ubuntu' => 'ubu', + 'android' => 'and', + 'ios' => 'ios', + 'chrome os' => 'chr', + 'chromeos' => 'chr', + ]; + + $os_lower = strtolower($value); + $os_icon = null; + + // Check if exact match exists in map + if (isset($os_map[$os_lower])) { + $os_icon = $os_map[$os_lower]; + } else { + // Check if value contains any of the keys + foreach ($os_map as $key => $icon) { + if (strpos($os_lower, $key) !== false) { + $os_icon = $icon; + break; + } + } + } + + if ($os_icon) { + $os_rel = '/admin/assets/images/os/' . $os_icon . '.webp'; + $os_path = SLIMSTAT_ANALYTICS_DIR . $os_rel; + if (is_readable($os_path)) { + $icon_url = SLIMSTAT_ANALYTICS_URL . $os_rel; + } + } + break; + + case 'username': + // For users, we'll use WordPress gravatar + // This will be handled separately in the JavaScript + break; } + + return $icon_url; } - public static function check_geoip_database() + /** + * AJAX handler to get distinct filter options for a selected dimension + */ + public static function get_filter_options() { - check_ajax_referer('wp_rest', 'security'); + check_ajax_referer('meta-box-order', 'security'); + + // If this user is whitelisted, we use the minimum capability + $minimum_capability = 'read'; + if (false === strpos(wp_slimstat::$settings['can_view'], (string) $GLOBALS['current_user']->user_login) && !empty(wp_slimstat::$settings['capability_can_view'])) { + $minimum_capability = wp_slimstat::$settings['capability_can_view']; + } + + if (!current_user_can($minimum_capability)) { + wp_send_json_error('Insufficient permissions'); + return; + } + + $dimension = sanitize_text_field($_POST['dimension'] ?? ''); + + // Validate dimension exists in columns_names + include_once(plugin_dir_path(__FILE__) . 'view/wp-slimstat-db.php'); + + // We only need the columns_names array, not the full init with filters + if (empty(wp_slimstat_db::$columns_names)) { + wp_slimstat_db::$columns_names = [ + 'id' => ['ID', 'number'], + 'ip' => ['IP', 'varchar'], + 'other_ip' => ['Other IP', 'varchar'], + 'username' => ['Username', 'varchar'], + 'email' => ['Email', 'varchar'], + 'country' => ['Country', 'varchar'], + 'location' => ['Location', 'varchar'], + 'city' => ['City', 'varchar'], + 'referer' => ['Referer', 'varchar'], + 'resource' => ['Resource', 'varchar'], + 'searchterms' => ['Search Terms', 'varchar'], + 'notes' => ['Notes', 'varchar'], + 'visit_id' => ['Visit ID', 'number'], + 'server_latency' => ['Server Latency', 'number'], + 'page_performance' => ['Page Performance', 'number'], + 'browser' => ['Browser', 'varchar'], + 'browser_version' => ['Browser Version', 'varchar'], + 'browser_type' => ['Browser Type', 'number'], + 'platform' => ['Platform', 'varchar'], + 'language' => ['Language', 'varchar'], + 'fingerprint' => ['Fingerprint', 'varchar'], + 'user_agent' => ['User Agent', 'varchar'], + 'resolution' => ['Resolution', 'varchar'], + 'screen_width' => ['Screen Width', 'number'], + 'screen_height' => ['Screen Height', 'number'], + 'content_type' => ['Content Type', 'varchar'], + 'category' => ['Category', 'varchar'], + 'author' => ['Author', 'varchar'], + 'content_id' => ['Content ID', 'number'], + 'outbound_resource' => ['Outbound Resource', 'varchar'], + 'tz_offset' => ['Timezone Offset', 'number'], + 'dt_out' => ['Date Time Out', 'number'], + 'dt' => ['Date Time', 'number'], + ]; + } + + if (empty($dimension) || !isset(wp_slimstat_db::$columns_names[$dimension])) { + wp_send_json_error('Invalid dimension'); + return; + } + + // Get time range parameters from AJAX request + $time_range_type = sanitize_text_field($_POST['time_range_type'] ?? 'last_28_days'); + $time_range_from = sanitize_text_field($_POST['time_range_from'] ?? ''); + $time_range_to = sanitize_text_field($_POST['time_range_to'] ?? ''); + + // Calculate time range timestamps + $time_start = null; + $time_end = null; + + if ($time_range_type === 'custom' && !empty($time_range_from) && !empty($time_range_to)) { + // Custom date range + $time_start = strtotime($time_range_from); + $time_end = strtotime($time_range_to . ' 23:59:59'); + } else { + // Preset date range + $preset_range = DateRangeHelper::get_range_by_preset($time_range_type); + if ($preset_range) { + $time_start = $preset_range['start']; + $time_end = $preset_range['end']; + } + } + + // Fallback to last 28 days if no valid time range + if (empty($time_start) || empty($time_end)) { + $preset_range = DateRangeHelper::get_range_by_preset('last_28_days'); + if ($preset_range) { + $time_start = $preset_range['start']; + $time_end = $preset_range['end']; + } + } + + // Get distinct values for this dimension via SlimStat\Utils\Query abstraction + $table_name = $GLOBALS['wpdb']->prefix . 'slim_stats'; + + // Limit results to prevent overwhelming the dropdown (filterable for customization) + $limit = apply_filters('slimstat_filter_options_limit', 500, $dimension); + $limit = absint($limit); // Ensure it's a positive integer + + // Enforce reasonable bounds to prevent abuse + if ($limit < 1 || $limit > 5000) { + $limit = 500; // Reset to default if out of reasonable range + } + + // Sanitize column name to prevent SQL injection (only allow known columns) + $allowed_columns = array_keys(wp_slimstat_db::$columns_names); + if (!in_array($dimension, $allowed_columns, true)) { + wp_send_json_error('Invalid column'); + return; + } + + // Additional sanitization layer for column name (defense in depth) + $safe_dimension = esc_sql($dimension); - try { - $geographicProvider = new GeoService(); + // Get distinct non-empty values + $column_type = wp_slimstat_db::$columns_names[$dimension][1]; - $result = $geographicProvider->checkDatabase(); + // Build SQL query directly to avoid Query class interference with global filters + $where_clauses = []; + + // Apply time range filter + if (!empty($time_start) && !empty($time_end)) { + $where_clauses[] = $GLOBALS['wpdb']->prepare('dt BETWEEN %d AND %d', intval($time_start), intval($time_end)); + } + + if ($column_type === 'varchar') { + // Exclude NULLs and empty strings for varchar columns + $where_clauses[] = $safe_dimension . ' IS NOT NULL'; + $where_clauses[] = $safe_dimension . " <> ''"; + } else { + // Exclude NULLs and zeros for numeric columns + $where_clauses[] = $safe_dimension . ' IS NOT NULL'; + $where_clauses[] = $safe_dimension . ' <> 0'; + } + + $where_sql = !empty($where_clauses) ? 'WHERE ' . implode(' AND ', $where_clauses) : ''; + + $sql = sprintf( + 'SELECT DISTINCT %s as value FROM %s %s ORDER BY %s ASC LIMIT %d', + $safe_dimension, + $table_name, + $where_sql, + $safe_dimension, + $limit + ); + + // Execute query + $results = $GLOBALS['wpdb']->get_results($sql, ARRAY_A); + + // Check for database errors + if ($GLOBALS['wpdb']->last_error) { + wp_send_json_error('Database query failed'); + return; + } + + // Ensure results is an array + if (!is_array($results)) { + $results = []; + } + + $options = []; + $seen_values = []; // Track values to prevent duplicates (case-insensitive) + $dimensions_with_icons = ['country', 'browser', 'language', 'platform', 'username']; + $has_icons = in_array($dimension, $dimensions_with_icons, true); + + foreach ($results as $row) { + if (!empty($row['value'])) { + // Sanitize output to prevent XSS + $sanitized_value = sanitize_text_field($row['value']); + + // Trim whitespace + $sanitized_value = trim($sanitized_value); + + // Skip empty values after trimming + if (empty($sanitized_value)) { + continue; + } + + // Check for duplicates using case-insensitive comparison + $value_key = strtolower($sanitized_value); + if (isset($seen_values[$value_key])) { + continue; // Skip duplicate + } + + // Mark this value as seen + $seen_values[$value_key] = true; + + // Limit individual option length to prevent DOM issues + if (strlen($sanitized_value) > 255) { + $sanitized_value = substr($sanitized_value, 0, 255) . '...'; + } + + if ($has_icons) { + // Return object with value and icon + $icon_url = self::get_filter_icon_url($dimension, $sanitized_value); + + // For username, get user gravatar + if ($dimension === 'username' && empty($icon_url)) { + $user = get_user_by('login', $sanitized_value); + if ($user) { + $icon_url = get_avatar_url($user->ID, ['size' => 32]); + } else { + $icon_url = get_avatar_url($sanitized_value, ['size' => 32]); + } + } + + $options[] = [ + 'value' => $sanitized_value, + 'label' => $sanitized_value, + 'icon' => $icon_url + ]; + } else { + // Return simple string for backward compatibility + $options[] = $sanitized_value; + } + } + } + + wp_send_json_success($options); + exit(); + } + + // END: get_filter_options + + public static function update_geoip_database() + { + check_ajax_referer('slimstat_geoip_action', 'security'); + + if (!current_user_can(\wp_slimstat::$settings['capability_can_admin'])) { + wp_send_json_error(__('Permission denied', 'wp-slimstat')); + return; + } + + try { + $provider = \wp_slimstat::$settings['geolocation_provider'] ?? 'maxmind'; + if ('cloudflare' === $provider) { + wp_send_json_success(__('Cloudflare geolocation does not require a database.', 'wp-slimstat')); + } + + // License validation is handled by the MaxMind provider; do not pre-check here + + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + $ok = $service->updateDatabase(); + + if ($ok) { + wp_send_json_success(__('GeoIP Database Successfully Updated!', 'wp-slimstat')); + } else { + // Log the error for debugging + $error_message = __('Failed to update GeoIP Database.', 'wp-slimstat'); + if ('maxmind' === $provider) { + $error_message .= ' ' . __('Please check your MaxMind license key and try again.', 'wp-slimstat'); + } + $geoip_error = get_option('slimstat_geoip_error', []); + if (!empty($geoip_error) && !empty($geoip_error['error'])) { + $error_message .= ' ' . sprintf(__('Details: %s', 'wp-slimstat'), $geoip_error['error']); + } + wp_send_json_error($error_message); + } + } catch (\Exception $exception) { + wp_send_json_error($exception->getMessage()); + } + } + + public static function check_geoip_database() + { + check_ajax_referer('slimstat_geoip_action', 'security'); + + if (!current_user_can(\wp_slimstat::$settings['capability_can_admin'])) { + wp_send_json_error(__('Permission denied', 'wp-slimstat')); + return; + } + + try { + $provider = \wp_slimstat::$settings['geolocation_provider'] ?? 'maxmind'; + if ('cloudflare' === $provider) { + wp_send_json_success(__('Cloudflare geolocation is active. No database to check.', 'wp-slimstat')); + } + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + $exists = file_exists($service->getProvider()->getDbPath()); + $result = [ 'notice' => $exists ? __('GeoIP Database is present and ready.', 'wp-slimstat') : __('GeoIP Database not found.', 'wp-slimstat') ]; wp_send_json_success($result['notice']); } catch (\Exception $exception) { @@ -1268,9 +2170,16 @@ $screen = get_current_screen(); if (stristr($screen->id, 'slimview')) { - wp_enqueue_script('feedbackbird-widget', 'https://cdn.jsdelivr.net/gh/feedbackbird/assets@master/wp/app.js?uid=01H5FBKA9Z5M2VJWQXZSX4Q7MS'); - wp_add_inline_script('feedbackbird-widget', sprintf('var feedbackBirdObject = %s;', json_encode([ - 'user_email' => function_exists('wp_get_current_user') ? wp_get_current_user()->user_email : '', + wp_register_script('feedbackbird-widget', 'https://cdn.jsdelivr.net/gh/feedbackbird/assets@master/wp/app.js?uid=01H5FBKA9Z5M2VJWQXZSX4Q7MS', [], null, true); + add_filter('script_loader_tag', function ($tag, $handle) { + if ('feedbackbird-widget' === $handle) { + $tag = str_replace('<script ', '<script defer ', $tag); + } + return $tag; + }, 10, 2); + wp_enqueue_script('feedbackbird-widget'); + wp_add_inline_script('feedbackbird-widget', sprintf('var feedbackBirdObject = %s;', wp_json_encode([ + 'user_email' => function_exists('wp_get_current_user') ? esc_attr(wp_get_current_user()->user_email) : '', 'platform' => 'wordpress-admin', 'config' => [ 'color' => '#e8294c', @@ -1301,9 +2210,9 @@ public static function get_template($template, $args = [], $return = false) { - // Push Args + // Push Args - use EXTR_SKIP to prevent variable overwriting for security if (is_array($args) && isset($args)) : - extract($args); + extract($args, EXTR_SKIP); endif; // Check Load single file or array list @@ -1329,7 +2238,7 @@ // include File include $template_file; } - + return null; } @@ -1345,7 +2254,6 @@ if (empty(\wp_slimstat_reports::$reports[$_report_id]['callback_args']) || !array_key_exists('raw', \wp_slimstat_reports::$reports[$_report_id]['callback_args'])) { return $_header_buttons; } - $utm_medium = empty($_report_id) ? 'report-unknown' : $_report_id; return '<a class="slimstat-upgrade-pro slimstat-filter-link slimstat-filter-temp button-export-to-xls slimstat-font-download is-not-pro noslimstat" title="' . __('Upgrade to Pro', 'wp-slimstat-pro') . '" href="https://wp-slimstat.com/pricing/?utm_source=admin&utm_medium=' . $utm_medium . '&utm_campaign=export" target="_blank"><span class="dashicons dashicons-download"></span>' . __('Export', 'wp-slimstat-pro') . '</a> ' . $_header_buttons; } @@ -1355,9 +2263,285 @@ if (isset($_GET['page']) && ('slimlayout' === $_GET['page'] || 'slimconfig' === $_GET['page'])) { return self::get_template('header', ['is_pro' => wp_slimstat::pro_is_installed()]); } - + return null; } -} + public static function ajax_add_country_dt_index() + { + check_ajax_referer('slimstat_add_country_dt_index'); + global $wpdb; + $table = $wpdb->prefix . 'slim_stats'; + $has_index = $wpdb->get_results(sprintf("SHOW INDEX FROM %s WHERE Key_name = 'idx_country_dt'", $table)); + if ($has_index && count($has_index) > 0) { + update_option('slimstat_country_dt_indexed', 'yes'); + wp_send_json_success(__('Index already exists.', 'wp-slimstat')); + } + $result = $wpdb->query(sprintf('CREATE INDEX idx_country_dt ON %s (country, dt)', $table)); + if (false !== $result) { + update_option('slimstat_country_dt_indexed', 'yes'); + wp_send_json_success(__('Index added successfully.', 'wp-slimstat')); + } else { + wp_send_json_error(__('Unable to add index or it already exists.', 'wp-slimstat')); + } + } + + public static function register_country_dt_index_hooks() + { + add_action('wp_ajax_slimstat_add_country_dt_index', [self::class, 'ajax_add_country_dt_index']); + } + + public static function ajax_add_dt_screen_index() + { + check_ajax_referer('slimstat_add_dt_screen_index'); + global $wpdb; + $table = $wpdb->prefix . 'slim_stats'; + $index_name = 'idx_dt_screen_width_screen_height'; + $has_index = $wpdb->get_results(sprintf("SHOW INDEX FROM %s WHERE Key_name = '%s'", $table, $index_name)); + if ($has_index && count($has_index) > 0) { + update_option('slimstat_dt_screen_indexed', 'yes'); + wp_send_json_success(__('Index already exists.', 'wp-slimstat')); + } + $result = $wpdb->query(sprintf('CREATE INDEX %s ON %s (dt, screen_width, screen_height)', $index_name, $table)); + if (false !== $result) { + update_option('slimstat_dt_screen_indexed', 'yes'); + wp_send_json_success(__('Index added successfully.', 'wp-slimstat')); + } else { + wp_send_json_error(__('Unable to add index or it already exists.', 'wp-slimstat')); + } + } + + public static function register_dt_screen_index_hooks() + { + add_action('wp_ajax_slimstat_add_dt_screen_index', [self::class, 'ajax_add_dt_screen_index']); + } + + public static function ajax_add_dt_browser_index() + { + check_ajax_referer('slimstat_add_dt_browser_index'); + global $wpdb; + $table = $wpdb->prefix . 'slim_stats'; + $index_name = 'idx_dt_browser_browser_version'; + $has_index = $wpdb->get_results(sprintf("SHOW INDEX FROM %s WHERE Key_name = '%s'", $table, $index_name)); + if ($has_index && count($has_index) > 0) { + update_option('slimstat_dt_browser_indexed', 'yes'); + wp_send_json_success(__('Index already exists.', 'wp-slimstat')); + } + $result = $wpdb->query(sprintf('CREATE INDEX %s ON %s (dt, browser, browser_version)', $index_name, $table)); + if (false !== $result) { + update_option('slimstat_dt_browser_indexed', 'yes'); + wp_send_json_success(__('Index added successfully.', 'wp-slimstat')); + } else { + wp_send_json_error(__('Unable to add index or it already exists.', 'wp-slimstat')); + } + } + + public static function register_dt_browser_index_hooks() + { + add_action('wp_ajax_slimstat_add_dt_browser_index', [self::class, 'ajax_add_dt_browser_index']); + } + + public static function ajax_add_dt_platform_index() + { + check_ajax_referer('slimstat_add_dt_platform_index'); + global $wpdb; + $table = $wpdb->prefix . 'slim_stats'; + $index_name = 'idx_dt_platform'; + $has_index = $wpdb->get_results(sprintf("SHOW INDEX FROM %s WHERE Key_name = '%s'", $table, $index_name)); + if ($has_index && count($has_index) > 0) { + update_option('slimstat_dt_platform_indexed', 'yes'); + wp_send_json_success(__('Index already exists.', 'wp-slimstat')); + } + $result = $wpdb->query(sprintf('CREATE INDEX %s ON %s (dt, platform)', $index_name, $table)); + if (false !== $result) { + update_option('slimstat_dt_platform_indexed', 'yes'); + wp_send_json_success(__('Index added successfully.', 'wp-slimstat')); + } else { + wp_send_json_error(__('Unable to add index or it already exists.', 'wp-slimstat')); + } + } + + public static function register_dt_platform_index_hooks() + { + add_action('wp_ajax_slimstat_add_dt_platform_index', [self::class, 'ajax_add_dt_platform_index']); + } + + public static function ajax_add_dt_out_index() + { + global $wpdb; + check_ajax_referer('slimstat_add_dt_out_index'); + + $table = $wpdb->prefix . 'slim_stats'; + $index_name = 'idx_dt_out'; + $has_index = $wpdb->get_results(sprintf("SHOW INDEX FROM %s WHERE Key_name = '%s'", $table, $index_name)); + if ($has_index && count($has_index) > 0) { + update_option('slimstat_dt_out_indexed', 'yes'); + wp_send_json_success(__('Index already exists.', 'wp-slimstat')); + } + + $result = $wpdb->query(sprintf('CREATE INDEX %s ON %s (dt_out)', $index_name, $table)); + if ($result) { + update_option('slimstat_dt_out_indexed', 'yes'); + wp_send_json_success(__('Index added successfully.', 'wp-slimstat')); + } + wp_send_json_error(__('Unable to add index or it already exists.', 'wp-slimstat')); + } + + public static function register_dt_out_index_hooks() + { + add_action('wp_ajax_slimstat_add_dt_out_index', [self::class, 'ajax_add_dt_out_index']); + } + + public static function show_indexes_notice() + { + // If new migration system is active, suppress legacy performance notice + if (class_exists(\SlimStat\Migration\Admin\MigrationAdmin::class)) { + return; + } + + if (!current_user_can('manage_options')) { + return; + } + $indexes = [ + [ + 'option' => 'slimstat_dt_out_indexed', + 'id' => 'dt-out', + 'label' => __('Currently Online Reports', 'wp-slimstat'), + 'desc' => __('Index on <code>dt_out</code>', 'wp-slimstat'), + 'key' => 'idx_dt_out', + 'ajax' => 'slimstat_add_dt_out_index', + 'btn' => __('Apply', 'wp-slimstat'), + ], + [ + 'option' => 'slimstat_country_dt_indexed', + 'id' => 'country-dt', + 'label' => __('World Map & Country Reports', 'wp-slimstat'), + 'desc' => __('Index on <code>country</code> and <code>dt</code>', 'wp-slimstat'), + 'key' => 'idx_country_dt', + 'ajax' => 'slimstat_add_country_dt_index', + 'btn' => __('Apply', 'wp-slimstat'), + ], + [ + 'option' => 'slimstat_dt_screen_indexed', + 'id' => 'dt-screen', + 'label' => __('Screen Resolution Reports', 'wp-slimstat'), + 'desc' => __('Index on <code>dt</code>, <code>screen_width</code>, <code>screen_height</code>', 'wp-slimstat'), + 'key' => 'idx_dt_screen_width_screen_height', + 'ajax' => 'slimstat_add_dt_screen_index', + 'btn' => __('Apply', 'wp-slimstat'), + ], + [ + 'option' => 'slimstat_dt_browser_indexed', + 'id' => 'dt-browser', + 'label' => __('Browser Reports', 'wp-slimstat'), + 'desc' => __('Index on <code>dt</code>, <code>browser</code>, <code>browser_version</code>', 'wp-slimstat'), + 'key' => 'idx_dt_browser_browser_version', + 'ajax' => 'slimstat_add_dt_browser_index', + 'btn' => __('Apply', 'wp-slimstat'), + ], + [ + 'option' => 'slimstat_dt_platform_indexed', + 'id' => 'dt-platform', + 'label' => __('Platform Reports', 'wp-slimstat'), + 'desc' => __('Index on <code>dt</code>, <code>platform</code>', 'wp-slimstat'), + 'key' => 'idx_dt_platform', + 'ajax' => 'slimstat_add_dt_platform_index', + 'btn' => __('Apply', 'wp-slimstat'), + ], + ]; + + $pending = array_filter($indexes, function ($idx) { + global $wpdb; + $exists = $wpdb->get_results(sprintf("SHOW INDEX FROM %sslim_stats WHERE Key_name = '%s'", $wpdb->prefix, $idx['key'])); + return empty($exists); + }); + if ([] === $pending) { + return; + } + $ajax_url = admin_url('admin-ajax.php'); + + // Generate nonces for each AJAX action + $nonces = []; + foreach ($pending as $idx) { + $nonces[$idx['ajax']] = wp_create_nonce($idx['ajax']); + } + + echo '<div class="notice slimstat-indexes-notice slimstat-notice" style="border-left: 6px solid #0073aa; background: #fff; box-shadow: 0 2px 8px #0001; padding: 24px 24px 16px 24px; margin-bottom: 24px; position: relative; min-width: 400px; max-width: 700px;">'; + echo '<h2 style="margin-top:0; font-size:1.3em; color:#0073aa;">' . __('Improve SlimStat Report Performance', 'wp-slimstat') . '</h2>'; + echo '<p style="margin-bottom:18px;">' . __('To speed up SlimStat reports, please apply the following database optimizations. These changes are safe and will not affect your data.', 'wp-slimstat') . '</p>'; + echo '<ul id="slimstat-index-list" style="list-style:none; margin:0 0 18px 0; padding:0;">'; + foreach ($pending as $idx) { + echo '<li id="slimstat-index-' . $idx['id'] . '" style="margin-bottom:12px; display:flex; align-items:center;">' + . '<div style="flex:1 1 0;">' + . '<div class="slimstat-index-label" style="font-weight:600;">' . $idx['label'] . '</div>' + . '<div class="slimstat-index-desc" style="color:#666; font-size:0.97em; margin-top:2px;">' . $idx['desc'] . '</div>' + . '</div>' + . '<span class="slimstat-index-lamp" style="margin-left:18px; min-width:30px; display:inline-block; font-size:1.5em; vertical-align:middle;">' + . '<span class="dashicons dashicons-lightbulb" style="color:#ccc;"></span>' + . '</span>' + . '<span class="slimstat-index-status" style="margin-left:10px; min-width:120px; display:inline-block;"></span>' + . '</li>'; + } + echo '</ul>'; + echo '<div id="slimstat-index-progress-bar" style="height:8px; background:#e5e5e5; border-radius:4px; overflow:hidden; margin-bottom:10px;">' + . '<div id="slimstat-index-progress" style="height:100%; width:0; background:linear-gradient(90deg,#0073aa,#00c3aa); transition:width 0.4s;"></div>' + . '</div>'; + echo '<button class="button button-primary" id="slimstat-apply-all" style="margin-bottom:10px; min-width:120px; font-size:1.1em;">' . __('Apply All', 'wp-slimstat') . '</button>'; + echo '<div style="color:#888; font-size:0.95em;">' . __('Do not close this tab until all optimizations are complete.', 'wp-slimstat') . '</div>'; + echo '</div>'; + ?> + <script> + jQuery(function($){ + var indexes = <?php echo wp_json_encode(array_values($pending)); ?>; + var nonces = <?php echo wp_json_encode($nonces); ?>; + var total = indexes.length, done = 0; + function updateProgress() { + var percent = Math.round((done/total)*100); + $('#slimstat-index-progress').css('width', percent+'%'); + if (done === total) setTimeout(function(){ $('.slimstat-indexes-notice').fadeOut(); }, 2000); + } + function markDone(id) { + var lamp = $('#slimstat-index-'+id+' .slimstat-index-lamp .dashicons'); + lamp.css('color','#ffc107'); // yellow lamp + lamp.addClass('slimstat-lamp-on'); + $('#slimstat-index-'+id).css('opacity',0.9); + } + $('#slimstat-apply-all').on('click', function(e){ + e.preventDefault(); + var btn = $(this); + btn.prop('disabled', true); + window.onbeforeunload = function(){ return '<?php echo esc_js(__('Please wait for SlimStat optimizations to finish.', 'wp-slimstat')); ?>'; }; + function next(i) { + if (i >= indexes.length) { + window.onbeforeunload = null; + return; + } + var idx = indexes[i]; + var li = $('#slimstat-index-'+idx.id); + li.find('.slimstat-index-status').html('<span style="color:#0073aa;">' + '<?php echo esc_js(__('In progress...', 'wp-slimstat')); ?>' + '</span> <span class="spinner is-active" style="float:none;display:inline-block;vertical-align:middle;"></span>'); + $.post('<?php echo $ajax_url; ?>', { + action: idx.ajax, + _ajax_nonce: nonces[idx.ajax] + }, function(response){ + if (response.success) { + markDone(idx.id); + done++; + li.find('.slimstat-index-status').html('<span style="color:green;">' + '<?php echo esc_js(__('Done!', 'wp-slimstat')); ?>' + '</span>'); + updateProgress(); + next(i+1); + } else { + li.find('.slimstat-index-status').html('<span style="color:red;">' + '<?php echo esc_js(__('Error: ', 'wp-slimstat')); ?>' + '</span>' + (response.data || '')); + btn.prop('disabled', false); + window.onbeforeunload = null; + } + }); + } + next(0); + }); + }); + </script> + <?php + } + +} // END: class declaration @@ -27,7 +27,8 @@ } $at_least_one_add_on_active = false; -$list_addons = @unserialize($response['body']); +// Security: Use JSON decode only to prevent PHP Object Injection +$list_addons = json_decode($response['body'], true); if (!is_array($list_addons)) { $error_message = __('There was an error decoding the add-ons list from the server. Please try again later.', 'wp-slimstat'); Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/admin/view: email-report.php @@ -3,13 +3,12 @@ exit(); } -// Load header -wp_slimstat_admin::get_template('header', ['is_pro' => wp_slimstat::pro_is_installed()]); +use SlimStat\Components\DateRangeHelper; ?> <div class="backdrop-container"> <div class="wrap slimstat"> - <h2><?php echo isset($_GET['page']) && isset(wp_slimstat_admin::$screens_info[sanitize_key($_GET['page'])]) ? esc_html(wp_slimstat_admin::$screens_info[sanitize_key($_GET['page'])]['title']) : '' ?></h2> + <?php wp_slimstat_admin::get_template('header', ['is_pro' => wp_slimstat::pro_is_installed()]); ?> <div class="notice slimstat-notice slimstat-tooltip-content" style="background-color:#ffa;border:0;padding:10px"><?php _e('<strong>AdBlock browser extension detected</strong> - If you see this notice, it means that your browser is not loading our stylesheet and/or Javascript files correctly. This could be caused by an overzealous ad blocker feature enabled in your browser (AdBlock Plus and friends). <a href="https://wp-slimstat.com/resources/the-reports-are-not-being-rendered-correctly-or-buttons-do-not-work" target="_blank">Please make sure to add an exception</a> to your configuration and allow the browser to load these assets.', 'wp-slimstat'); ?></div> @@ -27,7 +26,9 @@ } $filter_operator_html .= '</select></div>'; -$filter_value_html = '<div class="form-field"><input type="text" class="text" name="v" id="slimstat-filter-value" value="" size="20"></div>'; +$filter_value_html = '<div class="form-field"> + <input type="text" class="text" name="v" id="slimstat-filter-value" value="" size="20"> +</div>'; if ('on' == wp_slimstat::$settings['enable_sov']) { echo $filter_value_html . $filter_operator_html . $filter_name_html; @@ -39,103 +40,72 @@ $saved_filters = get_option('slimstat_filters', []); if (!empty($saved_filters)) { - echo '<a href="#" id="slimstat-load-saved-filters" class="button-secondary noslimstat" title="Saved Filters">' . __('Load', 'wp-slimstat') . '</a>'; + echo '<a href="#" id="slimstat-load-saved-filters" class="button-secondary noslimstat" title="Saved Filters">' . __('Saved Filters', 'wp-slimstat') . '</a>'; } ?></fieldset><!-- #slimstat-filters --> <fieldset id="slimstat-date-filters" class="wp-ui-highlight"> - <a href="#" class="noslimstat"><?php - if (!empty(wp_slimstat_db::$filters_normalized['date']['hour']) || !empty(wp_slimstat_db::$filters_normalized['date']['interval_hours'])) { - echo gmdate(get_option('date_format') . ' ' . get_option('time_format'), wp_slimstat_db::$filters_normalized['utime']['start']) . ' - '; - - $end_format = (date('Ymd', wp_slimstat_db::$filters_normalized['utime']['start']) !== date('Ymd', wp_slimstat_db::$filters_normalized['utime']['end'])) ? get_option('date_format') . ' ' . get_option('time_format') : get_option('time_format'); - echo gmdate($end_format, wp_slimstat_db::$filters_normalized['utime']['end']); - } else { - $start_date = gmdate(get_option('date_format'), wp_slimstat_db::$filters_normalized['utime']['start']); - $end_date = gmdate(get_option('date_format'), wp_slimstat_db::$filters_normalized['utime']['end']); - - if ($start_date === $end_date) { - echo ucwords($start_date); - } else { - echo ucwords($start_date) . ' – ' . ucwords($end_date); - } - } -?></a> - <div class="dropdown"> - <div id="slimstat-quick-filters"> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals today&&&interval equals -1') ?>"><?php _e('Today', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals yesterday&&&interval equals -1') ?>"><?php _e('Yesterday', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals today&&&interval equals -7') ?>"><?php _e('Last 7 Days', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals today&&&interval equals -14') ?>"><?php _e('Last 2 weeks', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals today&&&interval equals -28') ?>"><?php _e('Last 4 weeks', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals today&&&interval equals -84') ?>"><?php _e('Last 12 weeks', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals today&&&interval equals -364') ?>"><?php _e('Last 12 months', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals today&&&interval equals -' . date('j')) ?>"><?php _e('This Month', 'wp-slimstat') ?></a> - <a class="slimstat-filter-link noslimstat" href="<?php echo wp_slimstat_reports::fs_url('strtotime equals last day of -1 month 00:00:00 + 1 day &&&interval equals -' . date('d', strtotime('last day of -1 month 23:59:59'))) ?>"><?php _e('Previous Month', 'wp-slimstat') ?></a> - </div> - - <strong><?php _e('Date Range', 'wp-slimstat') ?></strong> - - <label for="slimstat-filter-hour"><?php _e('Hour', 'wp-slimstat') ?></label> - <input type="text" name="hour" id="slimstat-filter-hour" placeholder="<?php _e('Hour', 'wp-slimstat') ?>" class="short" value=""> - - <label for="slimstat-filter-day"><?php _e('Day', 'wp-slimstat') ?></label> - <input type="text" name="day" id="slimstat-filter-day" placeholder="<?php _e('Day', 'wp-slimstat') ?>" class="short" value=""> - - <label for="slimstat-filter-month"><?php _e('Month', 'wp-slimstat') ?></label> - <select name="month" id="slimstat-filter-month"> - <option value=""><?php _e('Month', 'wp-slimstat') ?></option><?php - for ($i = 1; $i <= 12; $i++) { - echo sprintf("<option value='%d'>", $i) . $GLOBALS['wp_locale']->get_month($i) . '</option>'; - } -?> - </select> - - <label for="slimstat-filter-year">Year</label> - <input type="text" name="year" id="slimstat-filter-year" placeholder="<?php _e('Year', 'wp-slimstat') ?>" class="short" value=""> - - <input type="hidden" class="slimstat-filter-date" name="slimstat-filter-date" value=""/> - <br/> - - <label for="slimstat-filter-interval"><?php _e('Days in interval', 'wp-slimstat') ?></label> - <input type="text" name="interval" id="slimstat-filter-interval" placeholder="<?php _e('± days', 'wp-slimstat') ?>" class="short" value="" title="<?php _e('To define an interval, enter the number of days (negative to go back in time).', 'wp-slimstat') ?>"> - - <label for="slimstat-filter-interval_hours"><?php _e('Hours in interval', 'wp-slimstat') ?></label> - <input type="text" name="interval_hours" id="slimstat-filter-interval_hours" placeholder="<?php _e('± hours', 'wp-slimstat') ?>" class="short" value=""> - - <input type="submit" value="<?php _e('Apply', 'wp-slimstat') ?>" class="button button-primary noslimstat right"> - - <?php - wp_slimstat::toggle_date_i18n_filters(false); - -if ( - wp_slimstat_db::$filters_normalized['date']['day'] != intval(date_i18n('j')) || wp_slimstat_db::$filters_normalized['date']['month'] != intval(date_i18n('n')) || wp_slimstat_db::$filters_normalized['date']['year'] != intval(date_i18n('Y')) || (wp_slimstat_db::$filters_normalized['date']['interval'] != -abs(wp_slimstat::$settings['posts_column_day_interval']) && wp_slimstat_db::$filters_normalized['date']['interval'] != -intval(date_i18n('j')) + 1) -) { - echo '<a class="slimstat-filter-link button button-secondary noslimstat" data-reset-filters="true" href="' . wp_slimstat_reports::fs_url() . '">' . __('Reset Filters', 'wp-slimstat') . '</a>'; -} -?> + <?php + // Get current date range for display + $current_range = DateRangeHelper::get_current_date_range(); + $display_label = DateRangeHelper::format_date_range($current_range['start'], $current_range['end'], $current_range['preset']); + ?> + + <!-- New Statistics-style Date Range Picker --> + <div class="slimstat-date-range-picker"> + <button type="button" class="slimstat-date-range-btn" aria-haspopup="true" aria-expanded="false"> + <div class="datepicker-badge-elements"> + <svg class="calendar-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"> + <defs> + <clipPath id="slimstat-calendar-clip"> + <path fill="#fff" d="M0 0h16v16H0z"/> + </clipPath> + </defs> + <g clip-path="url(#slimstat-calendar-clip)" stroke="currentColor" stroke-linejoin="round"> + <path d="M13 2.5H3a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5h10a.5.5 0 0 0 .5-.5V3a.5.5 0 0 0-.5-.5z"/> + <g stroke-linecap="round"> + <path d="M11 1.5v2m-6-2v2m-2.5 2h11"/> + </g> + </g> + </svg> + <span class="date-label"><?php echo esc_html($display_label); ?></span> + </div> + <div class="datepicker-badge-elements"> + <span class="caret"></span> + </div> + </button> + <input type="text" class="slimstat-date-range-input" style="display: none;" /> </div> - <div id="datepicker-backdrop"></div> </fieldset><!-- .slimstat-date-filters --> <?php foreach (wp_slimstat_db::$filters_normalized['columns'] as $a_key => $a_details) : ?> - <input type="hidden" name="fs[<?php echo esc_attr($a_key); ?>]" class="slimstat-post-filter" value="<?php echo htmlspecialchars($a_details[0] . ' ' . $a_details[1]) ?>"/> + <input type="hidden" name="fs[<?php echo esc_attr($a_key); ?>]" class="slimstat-post-filter" value="<?php echo esc_attr($a_details[0] . ' ' . $a_details[1]) ?>"/> <?php endforeach ?> <?php foreach (wp_slimstat_db::$filters_normalized['date'] as $a_key => $a_value) : if (!empty($a_value)) : ?> - <input type="hidden" name="fs[<?php echo esc_attr($a_key); ?>]" class="slimstat-post-filter" value="equals <?php echo htmlspecialchars($a_value) ?>"/> + <input type="hidden" name="fs[<?php echo esc_attr($a_key); ?>]" class="slimstat-post-filter" value="equals <?php echo esc_attr($a_value) ?>"/> <?php endif; endforeach; ?> <?php foreach (wp_slimstat_db::$filters_normalized['misc'] as $a_key => $a_value) : if (!empty($a_value)) : ?> - <input type="hidden" name="fs[<?php echo esc_attr($a_key); ?>]" class="slimstat-post-filter" value="equals <?php echo htmlspecialchars($a_value) ?>"/> + <input type="hidden" name="fs[<?php echo esc_attr($a_key); ?>]" class="slimstat-post-filter" value="equals <?php echo esc_attr($a_value) ?>"/> <?php endif; endforeach; ?> </form> <?php - if (('disable' == wp_slimstat::$settings['enable_maxmind'] || !\SlimStat\Services\GeoIP::database_exists()) && 'on' == wp_slimstat::$settings['notice_geolite']) { - wp_slimstat_admin::show_message(sprintf(__("GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor.", 'wp-slimstat'), self::$config_url . '2#wp-slimstat-third-party-libraries'), 'warning', 'geolite'); + // Provider-aware GeoIP notice: show only if a DB-based provider is selected and the database file is missing + $provider = wp_slimstat::$settings['geolocation_provider'] ?? 'dbip'; + $uses_db = in_array($provider, ['dbip', 'maxmind'], true); + if ($uses_db && 'on' == wp_slimstat::$settings['notice_geolite']) { + try { + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + if (!file_exists($service->getProvider()->getDbPath())) { + wp_slimstat_admin::show_message(sprintf(__("GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor.", 'wp-slimstat'), self::$config_url . '2#wp-slimstat-third-party-libraries'), 'warning', 'geolite'); + } + } catch (\Throwable $e) { + wp_slimstat_admin::show_message(sprintf(__("GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor.", 'wp-slimstat'), self::$config_url . '2#wp-slimstat-third-party-libraries'), 'warning', 'geolite'); + } } if (PHP_VERSION_ID >= 70100 && !file_exists(wp_slimstat::$upload_dir . '/browscap-cache-master/version.txt') && 'on' == wp_slimstat::$settings['notice_browscap']) { @@ -170,4 +140,4 @@ </div> </div> <div id="slimstat-modal-dialog"></div> -</div> \ No newline at end of file +</div> @@ -5,7 +5,11 @@ <div class="backdrop-container "> <div class="wrap slimstat slimstat-layout"> - <h2><?php _e('Customize and organize your reports', 'wp-slimstat') ?></h2> + <?php wp_slimstat_admin::get_template('header', ['is_pro' => wp_slimstat::pro_is_installed()]); ?> + + <h1 class="wp-heading-inline"><?php _e('Customize', 'wp-slimstat'); ?></h1> + <hr class="wp-header-end"> + <p><?php _e('You can drag and drop the placeholders here below from one widget area to another, to customize the layout of each report screen. You can place multiple charts on the same view, clone reports or move them to the Inactive Reports if you are not interested in that specific metric.', 'wp-slimstat'); if (is_network_admin()) { @@ -1,21 +1,169 @@ <!-- Header File--> +<?php -<div class="slimstat-header"> - <img src="<?php echo esc_url(plugin_dir_url(__FILE__) . '../../assets/images/white-slimstat-logo.png'); ?>" class="logo"/> +use SlimStat\Components\View; +use SlimStat\Services\Admin\Notification\NotificationFactory; - <?php if (isset($is_pro) && !$is_pro): ?> - <div class="vr-line"></div> - <div class="go-pro slimstat-upgrade-pro"> - <a href="<?php echo admin_url('admin.php?page=slimpro'); ?>"><?php esc_html_e('Go PRO', 'wp-slimstat'); ?><span class="icon"></span></a> - <p><?php esc_html_e('Upgrade to Pro to unlock more features', 'wp-slimstat'); ?></p> +$displayNotifications = (wp_slimstat::$settings['display_notifications'] == 'on'); +$hasUpdatedNotifications = false; +$newNotificationCount = 0; + +if ($displayNotifications && class_exists(NotificationFactory::class)) { + $hasUpdatedNotifications = NotificationFactory::hasUpdatedNotifications(); + $newNotificationCount = NotificationFactory::getNewNotificationCount(); +} + + + +$online_visitors = 0; + +if (class_exists('wp_slimstat_db')) { + global $wpdb; + $table = "{$wpdb->prefix}slim_stats"; + $current_minute_start = (int) floor(current_time('timestamp') / 60) * 60; + $window_minutes = 30; // 30 minutes - synced with Live Analytics Users Live + $window_start = $current_minute_start - (($window_minutes - 1) * 60); + + $sql = $wpdb->prepare( + " + SELECT COUNT(*) FROM ( + SELECT visit_id, MAX( + CASE + WHEN dt_out IS NOT NULL AND dt_out > 0 AND dt_out >= dt THEN dt_out + ELSE dt + END + ) AS last_activity + FROM {$table} + WHERE visit_id > 0 + AND ( + dt >= %d + OR ( dt_out IS NOT NULL AND dt_out >= %d ) + ) + GROUP BY visit_id + HAVING (FLOOR(last_activity / 60) * 60 + 59) >= %d + ) live_sessions + ", + $window_start, + $window_start, + $window_start + ); + + $online_visitors = (int) $wpdb->get_var($sql); +} + +$online_visitors = max(0, (int) $online_visitors); +$formatted_online_visitors = number_format_i18n($online_visitors); + +$support_url = 'https://wp-slimstat.com/contact/?utm_source=plugin&utm_medium=header&utm_campaign=support'; +$docs_url = 'https://wp-slimstat.com/resources/?utm_source=plugin&utm_medium=header&utm_campaign=docs'; +$settings_url = admin_url('admin.php?page=slimconfig'); +$upgrade_url = 'https://wp-slimstat.com/pricing/?utm_source=wp-slimstat&utm_medium=link&utm_campaign=header'; +$logo_url = plugin_dir_url(__FILE__) . '../../assets/images/white-slimstat-logo.png'; +?> + +<div class="slimstat-header slimstat-header--modern"> + <div class="slimstat-header__brand"> + <div class="slimstat-header__brand-copy"> + <svg width="118" height="30" viewBox="0 0 118 30" fill="none" xmlns="http://www.w3.org/2000/svg"> + <g clip-path="url(#clip0_1213_2675)"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M0 15C0 6.71582 6.7069 0 14.9801 0C20.2546 0 24.8865 2.72788 27.5572 6.84316L19.371 15.1743H19.3643V15.1877C19.0765 15.4893 18.5946 15.496 18.2934 15.2011C18.2599 15.1743 18.2331 15.1408 18.2064 15.1005L15.9239 11.9638C13.9627 9.27614 10.047 9.03485 7.77787 11.4678L0.589029 19.1756C0.194112 17.8217 0 16.4142 0 15ZM2.69079 23.5858C5.40167 27.4665 9.89302 30.0067 14.9801 30.0067C23.2533 30.0067 29.9602 23.2909 29.9602 15.0067C29.9602 13.7399 29.8062 12.5134 29.5117 11.3405L22.604 18.3646C20.3148 20.7172 16.466 20.4424 14.5316 17.7949L12.2491 14.6582C12.0015 14.3231 11.5329 14.2426 11.1916 14.4906C11.1514 14.5174 11.1179 14.5509 11.0845 14.5845L2.69079 23.5858Z" fill="#F22F46"/> + <path d="M43.3205 18.6394C42.2496 18.6394 41.2656 18.445 40.3754 18.0495C39.4851 17.6407 38.7288 17.0911 38.1063 16.394C37.4972 15.7171 37.042 14.9195 36.7676 14.0549L38.6953 13.264C39.1036 14.3565 39.706 15.1943 40.5093 15.7841C41.3259 16.3605 42.2763 16.6487 43.3674 16.6487C44.03 16.6487 44.6124 16.5415 45.1144 16.3337C45.6164 16.1259 45.9979 15.8243 46.2724 15.449C46.5602 15.0536 46.7008 14.6045 46.7008 14.0884C46.7008 13.378 46.4933 12.815 46.0916 12.4061C45.6967 11.9839 45.1144 11.6621 44.3446 11.4544L41.2388 10.5026C40.0139 10.1273 39.0768 9.52407 38.4276 8.70637C37.7783 7.88868 37.4503 6.95034 37.4503 5.89136C37.4503 4.96643 37.6712 4.15544 38.1063 3.46509C38.5614 2.75463 39.1772 2.19833 39.9671 1.80959C40.7703 1.40075 41.6739 1.19967 42.6846 1.19967C43.6954 1.19967 44.6124 1.38064 45.429 1.74257C46.259 2.1045 46.9618 2.60048 47.5374 3.2171C48.1064 3.81361 48.5415 4.52407 48.8092 5.30825L46.9016 6.09914C46.5401 5.14739 45.9912 4.43024 45.2683 3.94096C44.5454 3.44498 43.6887 3.19029 42.7047 3.19029C42.1023 3.19029 41.5601 3.29753 41.0983 3.5053C40.6632 3.68627 40.2884 3.99458 40.034 4.39002C39.793 4.77206 39.6726 5.22112 39.6726 5.75061C39.6726 6.37394 39.8667 6.92353 40.2616 7.40611C40.6565 7.88868 41.2522 8.26402 42.0555 8.51871L44.8868 9.35651C46.2188 9.76536 47.2228 10.3485 47.9056 11.1058C48.5883 11.8632 48.923 12.8015 48.923 13.9209C48.923 14.8458 48.682 15.6635 48.2001 16.3739C47.7316 17.0844 47.0756 17.6474 46.2255 18.0562C45.3955 18.4517 44.425 18.6461 43.3205 18.6461V18.6394ZM50.7905 18.3646V1.17957H52.899V18.3646H50.7905ZM55.0342 18.3646V6.13265H57.1426V18.3713H55.0342V18.3646ZM55.0342 4.17554V1.45436H57.1426V4.18225H55.0342V4.17554ZM59.2846 18.3646V6.13265H61.3462V8.63265L61.0517 8.24391C61.3529 7.47313 61.8348 6.88332 62.5041 6.47447C63.1668 6.06562 63.9165 5.86455 64.7465 5.86455C65.697 5.86455 66.5537 6.13265 67.3101 6.66214C68.0798 7.19163 68.6086 7.88868 68.8965 8.75329L68.3074 8.7734C68.622 7.82166 69.1642 7.1045 69.9139 6.61522C70.6568 6.11924 71.5337 5.85785 72.4306 5.86455C73.274 5.86455 74.0371 6.05892 74.7198 6.45436C75.4092 6.8431 75.9782 7.40611 76.3731 8.08975C76.7814 8.7868 76.9889 9.57099 76.9889 10.449V18.3713H74.8604V11.1259C74.8604 10.4423 74.7399 9.87259 74.4989 9.40343C74.258 8.93426 73.9233 8.57233 73.5016 8.31093C73.0933 8.03613 72.6114 7.90209 72.0491 7.90209C71.4868 7.90209 71.0116 8.03613 70.5765 8.31093C70.1548 8.56562 69.8135 8.94096 69.5591 9.42353C69.3181 9.8927 69.1977 10.4624 69.1977 11.1259V18.3713H67.0691V11.1259C67.0691 10.4423 66.9486 9.87259 66.7077 9.40343C66.4667 8.93426 66.132 8.57233 65.7103 8.31093C65.302 8.03613 64.8201 7.90209 64.2578 7.90209C63.7358 7.90209 63.2271 8.03613 62.7853 8.31093C62.3636 8.56562 62.0222 8.94096 61.7612 9.42353C61.5202 9.8927 61.3997 10.4624 61.3997 11.1259V18.3713H59.2913L59.2846 18.3646ZM84.9073 18.6394C83.8364 18.6394 82.8524 18.445 81.9622 18.0495C81.072 17.6407 80.3156 17.0911 79.6931 16.394C79.084 15.7171 78.6288 14.9195 78.3544 14.0549L80.2821 13.264C80.6904 14.3565 81.2929 15.1943 82.0961 15.7841C82.9127 16.3605 83.8632 16.6487 84.9542 16.6487C85.6169 16.6487 86.1992 16.5415 86.7012 16.3337C87.2032 16.1259 87.5848 15.8243 87.8592 15.449C88.147 15.0536 88.2876 14.6045 88.2876 14.0884C88.2876 13.378 88.0868 12.815 87.6785 12.4061C87.2836 11.9839 86.7012 11.6621 85.9315 11.4544L82.8257 10.5026C81.6007 10.1273 80.6637 9.52407 80.0144 8.70637C79.3651 7.88868 79.0371 6.95034 79.0371 5.89136C79.0371 4.96643 79.258 4.15544 79.6931 3.46509C80.1483 2.75463 80.7641 2.19833 81.5539 1.80959C82.3571 1.40075 83.2607 1.19967 84.2782 1.19967C85.2956 1.19967 86.2059 1.38064 87.0225 1.74257C87.8525 2.1045 88.5553 2.60048 89.131 3.2171C89.6999 3.81361 90.135 4.52407 90.4027 5.30825L88.4951 6.09914C88.1336 5.14739 87.5914 4.43024 86.8619 3.94096C86.139 3.44498 85.2822 3.19029 84.2982 3.19029C83.6958 3.19029 83.1536 3.29753 82.6851 3.5053C82.25 3.68627 81.8752 3.99458 81.6208 4.39002C81.3799 4.77206 81.2594 5.22112 81.2594 5.75061C81.2594 6.37394 81.4535 6.92353 81.8484 7.40611C82.2433 7.88868 82.8391 8.26402 83.6423 8.51871L86.4736 9.35651C87.8056 9.76536 88.8097 10.3485 89.4924 11.1058C90.1751 11.8632 90.5098 12.8015 90.5098 13.9209C90.5098 14.8458 90.2689 15.6635 89.7869 16.3739C89.3184 17.0844 88.6624 17.6474 87.8123 18.0562C86.9823 18.4517 86.0118 18.6461 84.9073 18.6461V18.6394ZM97.3439 18.5053C96.1525 18.5053 95.2288 18.1635 94.5795 17.4865C93.9436 16.8029 93.629 15.8444 93.629 14.6045V8.15678H91.4067V6.13265H91.8619C92.4041 6.13265 92.8392 5.96509 93.1538 5.63667C93.4684 5.30825 93.629 4.86589 93.629 4.31629V3.31764H95.7375V6.13265H98.4818V8.15678H95.7375V14.5375C95.7375 14.9463 95.7977 15.3015 95.9182 15.6032C96.0521 15.9048 96.2729 16.1461 96.5741 16.327C96.8754 16.4946 97.277 16.575 97.7723 16.575C97.8794 16.575 98.0066 16.5683 98.1605 16.5549C98.3278 16.5415 98.4818 16.5214 98.6157 16.508V18.3713C98.4015 18.4182 98.1806 18.4517 97.9597 18.4651C97.7589 18.4919 97.5514 18.5053 97.3506 18.512L97.3439 18.5053ZM103.663 18.6394C102.859 18.6394 102.15 18.4986 101.534 18.2104C100.932 17.9088 100.45 17.4999 100.108 16.9839C99.7603 16.4544 99.5862 15.8511 99.5862 15.1675C99.5862 14.4839 99.7201 13.9343 99.9945 13.4182C100.282 12.8887 100.717 12.4396 101.306 12.0777C101.909 11.7158 102.665 11.4544 103.576 11.3069L108.107 10.5562V12.3257L104.051 13.0093C103.261 13.1434 102.692 13.3981 102.331 13.76C101.983 14.1219 101.808 14.571 101.808 15.1005C101.808 15.6032 102.003 16.0187 102.398 16.3471C102.806 16.6822 103.315 16.8498 103.917 16.8498C104.687 16.8498 105.356 16.6889 105.912 16.3739C106.467 16.0589 106.936 15.5965 107.25 15.0335C107.585 14.4705 107.759 13.827 107.746 13.1702V10.0603C107.746 9.39672 107.498 8.85383 106.996 8.44498C106.514 8.02273 105.871 7.80825 105.068 7.80825C104.372 7.80825 103.756 7.98922 103.207 8.35115C102.685 8.69297 102.27 9.18225 102.029 9.75865L100.195 8.80691C100.423 8.24391 100.784 7.74793 101.286 7.30557C101.795 6.8431 102.391 6.48117 103.033 6.23989C103.696 5.9852 104.406 5.85115 105.122 5.85785C106.059 5.85785 106.882 6.03882 107.592 6.40074C108.301 6.74927 108.857 7.23855 109.245 7.87528C109.653 8.4986 109.854 9.22246 109.854 10.0536V18.3646H107.792V16.0522L108.181 16.1863C107.92 16.6755 107.565 17.1045 107.137 17.4597C106.675 17.8351 106.153 18.13 105.597 18.3243C105.008 18.5388 104.365 18.6394 103.669 18.6394H103.663ZM116.728 18.5053C115.537 18.5053 114.613 18.1635 113.964 17.4865C113.328 16.8029 113.013 15.8444 113.013 14.6045V8.15678H110.791V6.13265H111.246C111.789 6.13265 112.224 5.96509 112.538 5.63667C112.859 5.30155 113.013 4.86589 113.013 4.31629V3.31764H115.122V6.13265H117.866V8.15678H115.122V14.5375C115.122 14.9463 115.182 15.3015 115.303 15.6032C115.436 15.9048 115.657 16.1461 115.959 16.327C116.26 16.4946 116.661 16.575 117.157 16.575C117.264 16.575 117.391 16.5683 117.545 16.5549C117.712 16.5415 117.86 16.5214 118 16.508V18.3713C117.786 18.4182 117.565 18.4517 117.344 18.4651C117.143 18.4919 116.936 18.5053 116.735 18.512L116.728 18.5053Z" fill="#202224"/> + <path d="M37.2557 27.3458V22.5469H37.8916V24.9262L40.2678 22.5469H41.1246L39.1165 24.4906L41.2116 27.3458H40.3749L38.668 24.9195L37.8849 25.6836V27.3458H37.249H37.2557Z" fill="#202224"/> + <path d="M42.0684 27.3457V23.8672H42.5971V24.3632C42.8515 23.9811 43.2196 23.7935 43.7016 23.7935C43.9091 23.7935 44.1032 23.8337 44.2772 23.9074C44.4513 23.9811 44.5851 24.0817 44.6721 24.2023C44.7592 24.3229 44.8194 24.4704 44.8529 24.638C44.8729 24.7452 44.8863 24.9396 44.8863 25.2077V27.3457H44.2973V25.2345C44.2973 24.9932 44.2772 24.8122 44.2304 24.6983C44.1835 24.5776 44.1032 24.4838 43.9894 24.4101C43.8756 24.3364 43.735 24.3028 43.5811 24.3028C43.3334 24.3028 43.1125 24.3833 42.9318 24.5441C42.7511 24.705 42.6574 25.0066 42.6574 25.449V27.3457H42.0684Z" fill="#202224"/> + <path d="M45.9775 25.6098C45.9775 24.9664 46.1583 24.4905 46.513 24.1822C46.8142 23.9275 47.1757 23.7935 47.6041 23.7935C48.0793 23.7935 48.4742 23.9476 48.7754 24.2626C49.0766 24.5776 49.2306 25.0066 49.2306 25.5629C49.2306 26.012 49.1637 26.3605 49.0298 26.6152C48.8959 26.8699 48.7018 27.0709 48.4474 27.2117C48.1931 27.3524 47.912 27.4262 47.6108 27.4262C47.1221 27.4262 46.7339 27.272 46.4327 26.957C46.1315 26.642 45.9842 26.1929 45.9842 25.6098H45.9775ZM46.58 25.6098C46.58 26.0522 46.6804 26.3873 46.8678 26.6085C47.0619 26.8297 47.3029 26.9436 47.5974 26.9436C47.8919 26.9436 48.1328 26.8297 48.327 26.6085C48.5211 26.3873 48.6148 26.0455 48.6148 25.5897C48.6148 25.1607 48.5144 24.8323 48.3203 24.6112C48.1262 24.39 47.8852 24.276 47.5907 24.276C47.2962 24.276 47.0552 24.3833 46.8611 24.6044C46.667 24.8256 46.5733 25.1607 46.5733 25.6031L46.58 25.6098Z" fill="#202224"/> + <path d="M50.9578 27.3457L49.8936 23.8672H50.5027L51.0582 25.8712L51.2657 26.6152C51.2724 26.575 51.3327 26.3404 51.4465 25.898L52.002 23.8605H52.6044L53.1265 25.8779L53.3006 26.5414L53.5014 25.8712L54.0971 23.8605H54.666L53.5817 27.339H52.9726L52.417 25.2546L52.2831 24.6648L51.5803 27.339H50.9645L50.9578 27.3457Z" fill="#202224"/> + <path d="M57.8721 27.3458V22.5469H58.8292L59.9604 25.945C60.0675 26.26 60.1412 26.5013 60.188 26.6555C60.2416 26.4812 60.3286 26.2265 60.4424 25.8847L61.587 22.5469H62.437V27.3458H61.8279V23.3311L60.4357 27.3458H59.8667L58.4812 23.264V27.3458H57.8721Z" fill="#202224"/> + <path d="M63.5742 25.6098C63.5742 24.9664 63.7549 24.4905 64.1097 24.1822C64.4109 23.9275 64.7724 23.7935 65.2007 23.7935C65.676 23.7935 66.0709 23.9476 66.3721 24.2626C66.6733 24.5776 66.8273 25.0066 66.8273 25.5629C66.8273 26.012 66.7603 26.3605 66.6265 26.6152C66.4926 26.8699 66.2985 27.0709 66.0374 27.2117C65.7831 27.3524 65.502 27.4262 65.2007 27.4262C64.7121 27.4262 64.3239 27.272 64.0227 26.957C63.7215 26.642 63.5742 26.1929 63.5742 25.6098ZM64.1766 25.6098C64.1766 26.0522 64.2703 26.3873 64.4645 26.6085C64.6586 26.8297 64.8995 26.9436 65.194 26.9436C65.4886 26.9436 65.7295 26.8297 65.9236 26.6085C66.1178 26.3873 66.2115 26.0455 66.2115 25.5897C66.2115 25.1607 66.1111 24.8323 65.9169 24.6112C65.7228 24.39 65.4819 24.276 65.1874 24.276C64.8928 24.276 64.6519 24.3833 64.4578 24.6044C64.2637 24.8256 64.1699 25.1607 64.1699 25.6031L64.1766 25.6098Z" fill="#202224"/> + <path d="M67.912 27.3458V23.8673H68.4408V24.3968C68.5746 24.1488 68.7018 23.9879 68.8156 23.9075C68.9294 23.8271 69.0566 23.7869 69.1904 23.7869C69.3912 23.7869 69.592 23.8472 69.7928 23.9745L69.592 24.5241C69.4515 24.437 69.3042 24.3968 69.1637 24.3968C69.0365 24.3968 68.9227 24.437 68.8156 24.5107C68.7152 24.5912 68.6416 24.6984 68.5947 24.8324C68.5278 25.0402 68.4943 25.2681 68.4943 25.5228V27.3391H67.9053L67.912 27.3458Z" fill="#202224"/> + <path d="M72.9258 26.2264L73.5349 26.3002C73.4412 26.6554 73.2605 26.9302 72.9994 27.1313C72.7384 27.3323 72.4104 27.4262 72.0088 27.4262C71.5001 27.4262 71.0984 27.272 70.8039 26.957C70.5027 26.642 70.3555 26.2063 70.3555 25.6433C70.3555 25.0803 70.5027 24.6045 70.8039 24.2827C71.1051 23.961 71.4934 23.8002 71.9753 23.8002C72.4572 23.8002 72.8187 23.961 73.1132 24.276C73.4077 24.5911 73.555 25.0401 73.555 25.6098C73.555 25.6433 73.555 25.697 73.555 25.764H70.9646C70.9847 26.146 71.0918 26.4409 71.2859 26.642C71.48 26.8431 71.7209 26.9436 72.0088 26.9436C72.223 26.9436 72.4104 26.89 72.5643 26.776C72.7183 26.6621 72.8388 26.4811 72.9258 26.2331V26.2264ZM70.9914 25.2747H72.9325C72.9057 24.9798 72.8321 24.7653 72.7116 24.6179C72.5242 24.39 72.2832 24.276 71.982 24.276C71.7143 24.276 71.4867 24.3632 71.2993 24.5508C71.1185 24.7318 71.0114 24.9731 70.9914 25.2747Z" fill="#202224"/> + <path d="M74.8263 27.3458V26.6755H75.4956V27.3458C75.4956 27.5938 75.4488 27.7948 75.3618 27.9423C75.2748 28.0964 75.1342 28.2104 74.9468 28.2975L74.7861 28.0428C74.9133 27.9892 75.0003 27.9088 75.0606 27.8015C75.1208 27.6943 75.1543 27.5401 75.161 27.3458H74.8263Z" fill="#202224"/> + <path d="M79.0566 25.8042L79.6524 25.7506C79.6791 25.9919 79.7461 26.1863 79.8532 26.3404C79.9536 26.4946 80.1142 26.6152 80.3351 26.7158C80.556 26.8096 80.797 26.8565 81.0714 26.8565C81.3124 26.8565 81.5265 26.823 81.714 26.7493C81.9014 26.6755 82.0353 26.5817 82.129 26.4544C82.2227 26.327 82.2628 26.193 82.2628 26.0455C82.2628 25.8981 82.216 25.764 82.129 25.6501C82.0419 25.5361 81.9014 25.4423 81.7006 25.3686C81.5734 25.315 81.2856 25.2412 80.8505 25.134C80.4087 25.0268 80.1008 24.9262 79.9268 24.8324C79.6992 24.7117 79.5252 24.5643 79.4181 24.3833C79.3043 24.2024 79.2508 24.008 79.2508 23.7868C79.2508 23.5455 79.3177 23.3176 79.4583 23.1099C79.5988 22.9021 79.7929 22.7412 80.0607 22.6273C80.3284 22.5133 80.6162 22.4664 80.9375 22.4664C81.2923 22.4664 81.6069 22.52 81.8746 22.6407C82.1424 22.7546 82.3565 22.9222 82.4971 23.1434C82.6377 23.3646 82.718 23.6192 82.7314 23.9007L82.1223 23.9477C82.0888 23.6461 81.9817 23.4182 81.7876 23.2573C81.6002 23.1032 81.3191 23.0227 80.9509 23.0227C80.5828 23.0227 80.2882 23.0965 80.1142 23.2305C79.9402 23.3713 79.8532 23.5388 79.8532 23.7399C79.8532 23.9142 79.9134 24.0549 80.0406 24.1621C80.1611 24.2761 80.4824 24.39 80.9978 24.504C81.5132 24.6179 81.8679 24.7251 82.0553 24.8123C82.3365 24.9396 82.5373 25.1072 82.6711 25.3016C82.805 25.4959 82.8719 25.7238 82.8719 25.9852C82.8719 26.2466 82.7983 26.4812 82.6511 26.7091C82.5038 26.9369 82.2963 27.1112 82.0219 27.2385C81.7474 27.3659 81.4395 27.4262 81.0982 27.4262C80.6631 27.4262 80.3016 27.3659 80.0071 27.2385C79.7126 27.1112 79.485 26.9235 79.3177 26.6688C79.1503 26.4142 79.0633 26.1259 79.0566 25.8042Z" fill="#202224"/> + <path d="M84.0633 27.3458V22.5469H84.6524V24.2694C84.9268 23.9477 85.2749 23.7935 85.6899 23.7935C85.9442 23.7935 86.1718 23.8471 86.3592 23.9477C86.5466 24.0482 86.6872 24.189 86.7675 24.3699C86.8478 24.5509 86.888 24.8056 86.888 25.1474V27.3525H86.299V25.1474C86.299 24.8525 86.232 24.638 86.1049 24.504C85.9777 24.3699 85.797 24.3029 85.5627 24.3029C85.3887 24.3029 85.2213 24.3498 85.0741 24.437C84.9201 24.5308 84.813 24.6514 84.7461 24.8056C84.6791 24.9597 84.6457 25.1742 84.6457 25.449V27.3525H84.0566L84.0633 27.3458Z" fill="#202224"/> + <path d="M90.4495 26.9169C90.2287 27.1046 90.0212 27.2319 89.8204 27.3123C89.6195 27.3861 89.4054 27.4263 89.1711 27.4263C88.7896 27.4263 88.495 27.3324 88.2942 27.1448C88.0867 26.9571 87.9863 26.7225 87.9863 26.4276C87.9863 26.2601 88.0265 26.0992 88.1001 25.9584C88.1804 25.8177 88.2808 25.7038 88.4013 25.6166C88.5285 25.5295 88.6691 25.4692 88.823 25.4223C88.9368 25.3887 89.1108 25.3619 89.3451 25.3351C89.8204 25.2815 90.1684 25.2078 90.396 25.134C90.396 25.0536 90.396 25 90.396 24.9799C90.396 24.7386 90.3425 24.571 90.2287 24.4705C90.0814 24.3365 89.8538 24.2694 89.5593 24.2694C89.2849 24.2694 89.0774 24.3164 88.9435 24.4169C88.8096 24.5107 88.7159 24.685 88.649 24.933L88.0733 24.8525C88.1269 24.6046 88.2139 24.4035 88.3344 24.256C88.4549 24.1019 88.6289 23.9879 88.8565 23.9075C89.0841 23.8271 89.3451 23.7869 89.6463 23.7869C89.9475 23.7869 90.1818 23.8204 90.3692 23.8941C90.5566 23.9678 90.6905 24.055 90.7775 24.1555C90.8645 24.2627 90.9248 24.3968 90.9583 24.5576C90.9783 24.6582 90.985 24.8391 90.985 25.1005V25.8847C90.985 26.4343 90.9984 26.7761 91.0252 26.9236C91.052 27.071 91.0988 27.2051 91.1724 27.3391H90.5566C90.4964 27.2185 90.4562 27.071 90.4362 26.9102L90.4495 26.9169ZM90.4027 25.6032C90.1885 25.6903 89.8672 25.7641 89.4388 25.8244C89.1979 25.8579 89.0238 25.8981 88.9234 25.945C88.823 25.992 88.7427 26.0523 88.6891 26.1394C88.6356 26.2198 88.6088 26.3137 88.6088 26.4209C88.6088 26.5751 88.6691 26.7091 88.7895 26.8164C88.91 26.9236 89.0841 26.9705 89.3116 26.9705C89.5392 26.9705 89.74 26.9236 89.9141 26.8231C90.0881 26.7225 90.222 26.5885 90.3023 26.4142C90.3625 26.2802 90.396 26.0858 90.396 25.8244V25.6099L90.4027 25.6032Z" fill="#202224"/> + <path d="M92.3036 27.3458V23.8673H92.8324V24.3968C92.9662 24.1488 93.0934 23.9879 93.2072 23.9075C93.321 23.8271 93.4482 23.7869 93.582 23.7869C93.7828 23.7869 93.9836 23.8472 94.1845 23.9745L93.9836 24.5241C93.8431 24.437 93.6958 24.3968 93.5553 24.3968C93.4281 24.3968 93.3143 24.437 93.2072 24.5107C93.1068 24.5912 93.0332 24.6984 92.9863 24.8324C92.9194 25.0402 92.8859 25.2681 92.8859 25.5228V27.3391H92.2969L92.3036 27.3458Z" fill="#202224"/> + <path d="M97.3164 26.2264L97.9255 26.3002C97.8318 26.6554 97.6511 26.9302 97.39 27.1313C97.129 27.3323 96.801 27.4262 96.3994 27.4262C95.8907 27.4262 95.4891 27.272 95.1946 26.957C94.8933 26.642 94.7461 26.2063 94.7461 25.6433C94.7461 25.0803 94.8933 24.6045 95.1946 24.2827C95.4958 23.961 95.884 23.8002 96.3659 23.8002C96.8479 23.8002 97.2093 23.961 97.5038 24.276C97.7983 24.5911 97.9456 25.0401 97.9456 25.6098C97.9456 25.6433 97.9456 25.697 97.9456 25.764H95.3552C95.3753 26.146 95.4824 26.4409 95.6765 26.642C95.8706 26.8431 96.1116 26.9436 96.3994 26.9436C96.6136 26.9436 96.801 26.89 96.955 26.776C97.1089 26.6621 97.2294 26.4811 97.3164 26.2331V26.2264ZM95.382 25.2747H97.3231C97.2963 24.9798 97.2227 24.7653 97.1022 24.6179C96.9148 24.39 96.6738 24.276 96.3726 24.276C96.1049 24.276 95.8773 24.3632 95.6899 24.5508C95.5092 24.7318 95.4021 24.9731 95.382 25.2747Z" fill="#202224"/> + <path d="M101.373 27.3458V22.5469H102.009V26.7761H104.372V27.3391H101.38L101.373 27.3458Z" fill="#202224"/> + <path d="M107.825 26.2264L108.434 26.3002C108.341 26.6554 108.16 26.9302 107.899 27.1313C107.638 27.3323 107.31 27.4262 106.908 27.4262C106.399 27.4262 105.998 27.272 105.703 26.957C105.402 26.642 105.255 26.2063 105.255 25.6433C105.255 25.0803 105.402 24.6045 105.703 24.2827C106.005 23.961 106.393 23.8002 106.875 23.8002C107.357 23.8002 107.718 23.961 108.013 24.276C108.307 24.5911 108.454 25.0401 108.454 25.6098C108.454 25.6433 108.454 25.697 108.454 25.764H105.864C105.884 26.146 105.991 26.4409 106.185 26.642C106.379 26.8431 106.62 26.9436 106.908 26.9436C107.122 26.9436 107.31 26.89 107.464 26.776C107.618 26.6621 107.738 26.4811 107.825 26.2331V26.2264ZM105.897 25.2747H107.839C107.812 24.9798 107.738 24.7653 107.618 24.6179C107.43 24.39 107.189 24.276 106.888 24.276C106.62 24.276 106.393 24.3632 106.205 24.5508C106.025 24.7318 105.918 24.9731 105.897 25.2747Z" fill="#202224"/> + <path d="M109.338 26.307L109.92 26.2131C109.954 26.4477 110.041 26.6287 110.195 26.7493C110.342 26.8767 110.556 26.937 110.824 26.937C111.092 26.937 111.292 26.8834 111.426 26.7694C111.56 26.6555 111.62 26.5281 111.62 26.3807C111.62 26.2466 111.56 26.1461 111.446 26.0657C111.366 26.0121 111.165 25.945 110.844 25.8646C110.416 25.7574 110.114 25.6635 109.947 25.5831C109.78 25.5027 109.652 25.3954 109.565 25.2547C109.478 25.1139 109.438 24.9598 109.438 24.7855C109.438 24.6314 109.472 24.4839 109.545 24.3566C109.619 24.2225 109.713 24.1153 109.833 24.0281C109.927 23.9611 110.047 23.9008 110.208 23.8539C110.369 23.807 110.536 23.7802 110.717 23.7802C110.991 23.7802 111.225 23.8204 111.433 23.9008C111.64 23.9812 111.788 24.0885 111.888 24.2225C111.989 24.3566 112.055 24.5375 112.089 24.7587L111.513 24.8391C111.487 24.6582 111.413 24.5174 111.286 24.4236C111.159 24.323 110.984 24.2761 110.757 24.2761C110.489 24.2761 110.295 24.323 110.181 24.4102C110.067 24.4973 110.007 24.6045 110.007 24.7252C110.007 24.7989 110.034 24.8726 110.081 24.933C110.128 24.9933 110.201 25.0469 110.308 25.0871C110.369 25.1072 110.543 25.1608 110.831 25.2346C111.246 25.3485 111.54 25.4356 111.701 25.5094C111.868 25.5831 111.995 25.6836 112.089 25.8177C112.183 25.9517 112.229 26.1193 112.229 26.3204C112.229 26.5214 112.176 26.7024 112.055 26.8767C111.942 27.0509 111.774 27.185 111.56 27.2788C111.346 27.3726 111.098 27.4196 110.831 27.4196C110.382 27.4196 110.034 27.3257 109.8 27.1381C109.565 26.9504 109.412 26.6689 109.345 26.3003L109.338 26.307Z" fill="#202224"/> + <path d="M113.087 26.307L113.669 26.2131C113.703 26.4477 113.79 26.6287 113.944 26.7493C114.091 26.8767 114.305 26.937 114.573 26.937C114.841 26.937 115.041 26.8834 115.175 26.7694C115.309 26.6555 115.369 26.5281 115.369 26.3807C115.369 26.2466 115.309 26.1461 115.195 26.0657C115.115 26.0121 114.914 25.945 114.593 25.8646C114.165 25.7574 113.863 25.6635 113.696 25.5831C113.529 25.5027 113.402 25.3954 113.314 25.2547C113.227 25.1139 113.187 24.9598 113.187 24.7855C113.187 24.6314 113.221 24.4839 113.294 24.3566C113.368 24.2225 113.462 24.1153 113.582 24.0281C113.676 23.9611 113.796 23.9008 113.957 23.8539C114.118 23.807 114.285 23.7802 114.466 23.7802C114.74 23.7802 114.974 23.8204 115.182 23.9008C115.389 23.9812 115.537 24.0885 115.637 24.2225C115.738 24.3566 115.804 24.5375 115.838 24.7587L115.262 24.8391C115.236 24.6582 115.162 24.5174 115.035 24.4236C114.908 24.323 114.734 24.2761 114.506 24.2761C114.238 24.2761 114.044 24.323 113.93 24.4102C113.817 24.4973 113.756 24.6045 113.756 24.7252C113.756 24.7989 113.783 24.8726 113.83 24.933C113.877 24.9933 113.95 25.0469 114.057 25.0871C114.118 25.1072 114.292 25.1608 114.58 25.2346C114.995 25.3485 115.289 25.4356 115.45 25.5094C115.617 25.5831 115.744 25.6836 115.838 25.8177C115.932 25.9517 115.979 26.1193 115.979 26.3204C115.979 26.5214 115.925 26.7024 115.804 26.8767C115.691 27.0509 115.523 27.185 115.309 27.2788C115.095 27.3726 114.847 27.4196 114.58 27.4196C114.131 27.4196 113.783 27.3257 113.549 27.1381C113.314 26.9504 113.161 26.6689 113.094 26.3003L113.087 26.307Z" fill="#202224"/> + <path d="M117.236 27.3458V26.6755H117.906V27.3458H117.236Z" fill="#202224"/> + </g> + <defs> + <clipPath id="clip0_1213_2675"> + <rect width="118" height="30" fill="white"/> + </clipPath> + </defs> + </svg> </div> - <?php endif; ?> + </div> - <?php if (isset($is_pro) && $is_pro): ?> - <div class="pro-badge"> - <span class="icon"></span> - <p><?php esc_html_e('Pro is activated!', 'wp-slimstat'); ?></p> + <div class="slimstat-header__meta" role="presentation"> + <div class="slimstat-header__online" aria-live="polite"> + <span class="slimstat-header__online-icon" aria-hidden="true"> + <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> + <circle cx="9" cy="4.5" r="3" fill="#676E74"/> + <ellipse cx="9" cy="12.75" rx="5.25" ry="3" fill="#676E74"/> + </svg> + </span> + <span class="slimstat-header__online-label"><?php esc_html_e('Online Visitors', 'wp-slimstat'); ?></span> + <span id="slimstat-online-visitors-count" class="slimstat-header__online-value"><?php echo esc_html($formatted_online_visitors); ?></span> </div> - <?php endif; ?> + </div> + + <div class="slimstat-header__actions"> + <div class="slimstat-header__icon-bar"> + <?php if ($displayNotifications): ?> + <a href="#" class="slimstat-header__icon slimstat-header__icon--notifications slimstat-notifications js-slimstat-open-notification <?php echo $hasUpdatedNotifications ? esc_attr('slimstat-notifications--has-items') : ''; ?>" title="<?php esc_attr_e('Notifications', 'wp-slimstat'); ?>" aria-label="<?php esc_attr_e('Notifications', 'wp-slimstat'); ?>"> + <span class="dashicons dashicons-bell" aria-hidden="true"></span> + <?php if ($newNotificationCount > 0): ?> + <span class="slimstat-header__badge"><?php echo esc_html(number_format_i18n($newNotificationCount)); ?></span> + <?php endif; ?> + </a> + <?php endif; ?> +<!-- <button type="button" class="slimstat-header__icon slimstat-header__icon--feedback" data-slimstat-feedback-trigger data-slimstat-feedback-fallback="--><?php //echo esc_url($support_url); ?><!--" title="--><?php //esc_attr_e('Share Feedback', 'wp-slimstat'); ?><!--" aria-label="--><?php //esc_attr_e('Share Feedback', 'wp-slimstat'); ?><!--">--> +<!-- <span class="dashicons dashicons-format-chat" aria-hidden="true"></span>--> +<!-- <span class="screen-reader-text">--><?php //esc_html_e('Open feedback form', 'wp-slimstat'); ?><!--</span>--> +<!-- </button>--> + <button type="button" class="slimstat-header__icon slimstat-header__icon--help" data-slimstat-help-trigger data-slimstat-help-fallback="<?php echo esc_url($docs_url); ?>" title="<?php esc_attr_e('Help', 'wp-slimstat'); ?>" aria-label="<?php esc_attr_e('Help', 'wp-slimstat'); ?>"> + <span class="dashicons dashicons-editor-help" aria-hidden="true"></span> + <span class="screen-reader-text"><?php esc_html_e('Open help panel', 'wp-slimstat'); ?></span> + </button> + <a href="<?php echo esc_url($settings_url); ?>" class="slimstat-header__icon" title="<?php esc_attr_e('Settings', 'wp-slimstat'); ?>" aria-label="<?php esc_attr_e('Settings', 'wp-slimstat'); ?>"> + <span class="dashicons dashicons-admin-generic" aria-hidden="true"></span> + </a> + </div> + + <?php if (isset($is_pro) && $is_pro): ?> + <span class="slimstat-header__status-pill" aria-label="<?php esc_attr_e('Premium plan active', 'wp-slimstat'); ?>"> + <span class="slimstat-header__status-icon" aria-hidden="true"> + <svg width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M0.251677 2.05494C0 2.41352 0 3.47938 0 5.6111V6.66091C0 10.4196 2.82597 12.2437 4.59904 13.0182C5.08001 13.2283 5.32049 13.3333 6 13.3333C6.67951 13.3333 6.91999 13.2283 7.40096 13.0182C9.17402 12.2437 12 10.4196 12 6.66091V5.6111C12 3.47938 12 2.41352 11.7483 2.05494C11.4966 1.69636 10.4945 1.3533 8.49006 0.667195L8.10819 0.536478C7.06335 0.178826 6.54093 0 6 0C5.45907 0 4.93666 0.178826 3.89182 0.536478L3.50994 0.667195C1.50555 1.3533 0.503354 1.69636 0.251677 2.05494ZM8.03964 5.66634C8.22355 5.46036 8.20566 5.14428 7.99967 4.96036C7.79369 4.77645 7.47761 4.79434 7.2937 5.00033L5.28571 7.24927L4.7063 6.60033C4.52239 6.39434 4.20631 6.37645 4.00033 6.56036C3.79434 6.74428 3.77645 7.06036 3.96036 7.26634L4.91275 8.33301C5.00761 8.43925 5.14328 8.5 5.28571 8.5C5.42815 8.5 5.56382 8.43925 5.65868 8.33301L8.03964 5.66634Z" fill="#18A456"/> + </svg> + </span> + <span class="slimstat-header__status-text"><?php esc_html_e('Premium', 'wp-slimstat'); ?></span> + </span> + <?php else: ?> + <a href="<?php echo esc_url($upgrade_url); ?>" class="slimstat-header__cta slimstat-upgrade-pro" target="_blank" title="<?php esc_attr_e('Upgrade to Premium', 'wp-slimstat'); ?>"> + <span class="slimstat-header__cta-icon" aria-hidden="true"> + <svg width="11" height="13" viewBox="0 0 11 13" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path d="M7.50106 5.79266C6.8433 5.00565 6.51443 4.61214 6.13637 4.67275C5.75831 4.73336 5.58144 5.20795 5.22771 6.15713L5.1362 6.40269C5.03568 6.67242 4.98542 6.80728 4.88763 6.90662C4.78984 7.00597 4.65559 7.05855 4.38709 7.16371L4.14264 7.25945C3.19778 7.62952 2.72535 7.81455 2.67147 8.19275C2.61759 8.57094 3.01794 8.89184 3.81865 9.53363L4.0258 9.69967C4.25334 9.88205 4.3671 9.97324 4.43296 10.0981C4.49881 10.2229 4.5093 10.3672 4.53027 10.6558L4.54936 10.9186C4.62316 11.9343 4.66006 12.4422 5.00482 12.6153C5.34958 12.7885 5.77388 12.5122 6.62247 11.9597L6.84201 11.8167C7.08316 11.6597 7.20373 11.5812 7.34222 11.559C7.48071 11.5368 7.62144 11.5734 7.9029 11.6466L8.15915 11.7133C9.14962 11.971 9.64485 12.0998 9.91181 11.8286C10.1788 11.5574 10.0406 11.0658 9.76439 10.0825L9.69292 9.82816C9.61442 9.54874 9.57517 9.40903 9.59491 9.27049C9.61465 9.13195 9.69114 9.01026 9.84412 8.76688L9.98339 8.5453C10.5217 7.68883 10.7909 7.2606 10.6111 6.91986C10.4314 6.57912 9.9217 6.55154 8.90238 6.49637L8.63867 6.4821C8.34901 6.46642 8.20418 6.45858 8.07789 6.39516C7.9516 6.33174 7.85814 6.21992 7.67123 5.99627L7.50106 5.79266Z" fill="#202224"/> + <path d="M7.25194 2.11148L7.37396 2.45937C7.50798 2.84147 7.575 3.03253 7.70538 3.17327C7.83577 3.31401 8.01477 3.3885 8.37276 3.53748L8.6987 3.67311C9.95851 4.19737 10.5884 4.4595 10.6603 4.99528C10.6902 5.2183 10.6151 5.42726 10.4403 5.66079C10.3813 5.6448 10.3244 5.63177 10.2709 5.6209C9.92016 5.54958 9.47401 5.52554 9.01929 5.50104L8.6928 5.48338C8.63001 5.47998 8.58119 5.47732 8.53937 5.47461C8.51166 5.44219 8.47975 5.40403 8.43861 5.35481L8.2282 5.10303C7.93462 4.75159 7.64737 4.40773 7.38024 4.16902C7.09537 3.91444 6.61917 3.58241 5.97815 3.68518C5.33031 3.78904 4.98475 4.26042 4.79831 4.59686C4.62558 4.90857 4.47024 5.32575 4.31295 5.74812L4.19924 6.0533C4.18002 6.10486 4.16461 6.14617 4.15104 6.1817C4.11549 6.19596 4.07414 6.21217 4.02248 6.2324L3.71883 6.35132C3.29825 6.51592 2.88328 6.67832 2.5743 6.85611C2.24181 7.04742 1.77426 7.40077 1.68155 8.05153C1.58972 8.69609 1.93301 9.16663 2.19286 9.44567C2.38653 9.65363 2.64674 9.87301 2.92456 10.0978C1.87885 10.3815 1.32261 10.4874 1.00648 10.1461C0.650544 9.76194 0.834708 9.06545 1.20303 7.67249L1.29833 7.31211C1.40299 6.91627 1.45533 6.71835 1.42901 6.52209C1.40269 6.32582 1.30071 6.15342 1.09673 5.80863L0.91103 5.49473C0.193236 4.2814 -0.165661 3.67474 0.0740371 3.19202C0.313735 2.70931 0.993282 2.67023 2.35238 2.59208L2.70399 2.57186C3.09021 2.54965 3.28331 2.53855 3.4517 2.4487C3.62009 2.35886 3.7447 2.20044 3.99392 1.88361L4.22081 1.59516C5.09782 0.480225 5.53632 -0.0772439 6.0404 0.00862086C6.54448 0.0944856 6.7803 0.766818 7.25194 2.11148Z" fill="#202224"/> + </svg> + </span> + <span class="slimstat-header__cta-label"><?php esc_html_e('Upgrade to Premium', 'wp-slimstat'); ?></span> + <span class="slimstat-header__cta-arrow" aria-hidden="true"> + <svg width="4" height="8" viewBox="0 0 4 8" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M0.13096 0.0902856C0.288207 -0.0444977 0.524945 -0.0262872 0.659728 0.13096L3.65973 3.63096C3.7801 3.77139 3.7801 3.97862 3.65973 4.11905L0.659728 7.61905C0.524945 7.7763 0.288207 7.79451 0.13096 7.65973C-0.0262872 7.52494 -0.0444977 7.28821 0.0902856 7.13096L2.8811 3.87501L0.0902856 0.619053C-0.0444977 0.461806 -0.0262872 0.225069 0.13096 0.0902856Z" fill="#202224"/> + </svg> + </span> + </a> + <?php endif; ?> + </div> +</div> -</div> \ No newline at end of file +<?php +if ($displayNotifications && class_exists(NotificationFactory::class)) { + $notifications = NotificationFactory::getAllNotifications(); + View::load('components/notification/side-bar', ['notifications' => $notifications]); +} +?> \ No newline at end of file @@ -98,7 +98,5 @@ </div> </div> - <a target="_blank" href="<?php echo esc_url(SLIMSTAT_ANALYTICS_SITE); ?>" class="go-pro-button"><?php esc_html_e('Unlock SlimStat Pro', 'wp-slimstat'); ?></a> + <a target="_blank" href="<?php echo esc_url('https://wp-slimstat.com/pricing/?utm_source=wp-slimstat&utm_medium=link&utm_campaign=email'); ?>" class="go-pro-button"><?php esc_html_e('Unlock SlimStat Pro', 'wp-slimstat'); ?></a> </div> - -<div class="slimstat-pro-modal-backdrop"></div> \ No newline at end of file @@ -13,8 +13,11 @@ // - backlink: format of the URL point to the search engine result page // - charsets: list of charset used to encode the keywords // -$search_engines = file_get_contents(plugin_dir_path(dirname(__FILE__, 2)) . 'admin/assets/data/matomo-searchengine.json'); -$search_engines = json_decode($search_engines, true); +if (!class_exists('wp_slimstat')) { + include_once dirname(__FILE__, 3) . '/wp-slimstat.php'; +} + +$search_engines = \wp_slimstat::get_search_engines(); // COMPLETE THIS FEATURE!! // Available icons @@ -93,7 +96,7 @@ // City, if tracked $city_filter = ''; if (!empty($results[$i]['city'])) { - $city_filter = "<a class='slimstat-filter-link' href='" . wp_slimstat_reports::fs_url('city equals ' . $results[$i]['city']) . sprintf("'>%s</a>", $results[ $i ][ 'city' ]); + $city_filter = "<a class='slimstat-filter-link' href='" . esc_url(wp_slimstat_reports::fs_url('city equals ' . $results[$i]['city'])) . "'>" . esc_html($results[$i]['city']) . "</a>"; } // Browser @@ -107,7 +110,7 @@ $browser_filter = sanitize_title($results[$i]['browser']); } - $browser_filter = "<a class='slimstat-filter-link inline-icon' href='" . wp_slimstat_reports::fs_url('browser equals ' . $results[$i]['browser']) . sprintf("'><img class='slimstat-tooltip-trigger' src='%s/assets/images/browsers/%s.png' width='16' height='16' title='%s'></a>", $plugin_url, $browser_filter, $browser_title); + $browser_filter = "<a class='slimstat-filter-link inline-icon' href='" . esc_url(wp_slimstat_reports::fs_url('browser equals ' . $results[$i]['browser'])) . "'><img class='slimstat-tooltip-trigger' src='" . esc_url($plugin_url . '/assets/images/browsers/' . $browser_filter . '.png') . "' width='16' height='16' title='" . esc_attr($browser_title) . "'></a>"; // Operating System $platform_filter = 'unknown'; @@ -137,7 +140,7 @@ } if (empty($results[$i]['username'])) { - $ip_address = "<a class='slimstat-filter-link' href='" . wp_slimstat_reports::fs_url('ip equals ' . $results[$i]['ip']) . sprintf("'>%s</a>", $host_by_ip); + $ip_address = "<a class='slimstat-filter-link' href='" . esc_url(wp_slimstat_reports::fs_url('ip equals ' . $results[$i]['ip'])) . "'>" . esc_html($host_by_ip) . "</a>"; } else { $display_user_name = $results[$i]['username']; if ('on' == wp_slimstat::$settings['show_display_name'] && false !== strpos($results[$i]['notes'], 'user:')) { @@ -148,29 +151,37 @@ } $user = get_user_by('login', $results[$i]['username']); - $ip_address = "<a class='slimstat-filter-link' href='" . wp_slimstat_reports::fs_url('username equals ' . $results[$i]['username']) . "'>"; + $ip_address = "<a class='slimstat-filter-link' href='" . esc_url(wp_slimstat_reports::fs_url('username equals ' . $results[$i]['username'])) . "'>"; if ($user) { $ip_address .= get_avatar($user->ID, 16); } else { $ip_address .= get_avatar($results[$i]['username'], 16); } - $ip_address .= sprintf(' %s</a>', $display_user_name); + $ip_address .= ' ' . esc_html($display_user_name) . '</a>'; + $display_ip_value = $results[$i]['ip']; + if ('on' == (wp_slimstat::$settings['hash_ip'] ?? 'off')) { + $display_ip_value = substr($results[$i]['ip'], 0, 12) . '…'; + } elseif ('on' == wp_slimstat::$settings['anonymize_ip']) { + // already masked in storage; still truncate for UI clarity + $display_ip_value = $results[$i]['ip']; + } + $ip_address .= " <a class='slimstat-filter-link' href='" . wp_slimstat_reports::fs_url('ip equals ' . $results[$i]['ip']) - . sprintf("'>(%s)</a>", $host_by_ip); + . sprintf("'>(%s)</a>", esc_html($display_ip_value)); $highlight_row = (false !== strpos($results[$i]['notes'], 'user:')) ? ' is-known-user' : ' is-known-visitor'; } $whois_pin = ''; if (is_admin() && !empty(wp_slimstat::$settings['ip_lookup_service']) && !wp_slimstat::is_local_ip_address($results[$i]['ip'])) { - $whois_pin = "<a class='slimstat-font-location-1 whois' href='" . wp_slimstat::$settings['ip_lookup_service'] . sprintf("%s' target='_blank' title='WHOIS: %s'></a>", $results[ $i ][ 'ip' ], $results[ $i ][ 'ip' ]); + $whois_pin = "<a class='slimstat-font-location-1 whois' href='" . esc_url(wp_slimstat::$settings['ip_lookup_service'] . $results[$i]['ip']) . "' target='_blank' title='WHOIS: " . esc_attr($results[$i]['ip']) . "'></a>"; } // Originating IP Address $other_ip_address = ''; if (!empty($results[$i]['other_ip'])) { - $other_ip_address = "(<a class='slimstat-font-location-1 whois' href='" . wp_slimstat::$settings['ip_lookup_service'] . sprintf("%s' target='_blank' title='WHOIS: %s'></a> <a class='slimstat-filter-link' href='", $results[ $i ][ 'other_ip' ], $results[ $i ][ 'other_ip' ]) . wp_slimstat_reports::fs_url('other_ip equals ' . $results[$i]['other_ip']) . "'>" . __('Originating IP', 'wp-slimstat') . sprintf(': %s)</a>', $results[$i]['other_ip']); + $other_ip_address = "(<a class='slimstat-font-location-1 whois' href='" . esc_url(wp_slimstat::$settings['ip_lookup_service'] . $results[$i]['other_ip']) . "' target='_blank' title='WHOIS: " . esc_attr($results[$i]['other_ip']) . "'></a> <a class='slimstat-filter-link' href='" . esc_url(wp_slimstat_reports::fs_url('other_ip equals ' . $results[$i]['other_ip'])) . "'>" . __('Originating IP', 'wp-slimstat') . ': ' . esc_html($results[$i]['other_ip']) . ")</a>"; } // Screen Resolution @@ -182,7 +193,7 @@ // Fingerprint $fingerprint = ''; if (!$is_dashboard && !empty($results[$i]['fingerprint'])) { - $fingerprint = "<span class='pageview-screenres'><code><a class='slimstat-filter-link slimstat-tooltip-trigger' href='" . wp_slimstat_reports::fs_url('fingerprint equals ' . $results[$i]['fingerprint']) . "' title='" . $results[$i]['fingerprint'] . "'>" . substr($results[$i]['fingerprint'], 0, 8) . '</a></code></span>'; + $fingerprint = "<span class='pageview-screenres'><code><a class='slimstat-filter-link slimstat-tooltip-trigger' href='" . wp_slimstat_reports::fs_url('fingerprint equals ' . esc_attr($results[$i]['fingerprint'])) . "' title='" . esc_attr($results[$i]['fingerprint']) . "'>" . esc_html(substr($results[$i]['fingerprint'], 0, 8)) . '</a></code></span>'; } $row_output = sprintf("<p class='header%s'>%s %s %s %s %s %s %s %s %s %s %s</p>", $highlight_row, $browser_filter, $platform_filter, $browser_type_filter, $country_filter, $whois_pin, $city_filter, $ip_address, $other_ip_address, $fingerprint, $screen_resolution, $language_filter); @@ -221,6 +232,15 @@ $results[$i]['resource'] = __('Local search results page', 'wp-slimstat'); } + // Defensive: ensure 'searchterms' and 'referer' keys exist to avoid undefined index + if (!isset($results[$i]['searchterms'])) { + $results[$i]['searchterms'] = ''; + } + + if (!isset($results[$i]['referer'])) { + $results[$i]['referer'] = ''; + } + if (empty($search_terms_info)) { $search_terms_info = wp_slimstat_reports::get_search_terms_info($results[$i]['searchterms'], $results[$i]['referer']); } @@ -3,8 +3,6 @@ exit(); } -// Load header -wp_slimstat_admin::get_template('header', ['is_pro' => wp_slimstat::pro_is_installed()]); ?> <div class="backdrop-container"> <?php @@ -12,6 +10,7 @@ wp_slimstat_admin::get_template('slimstat-pro-modal'); ?> <div class="wrap slimstat upgrade-pro"> + <?php wp_slimstat_admin::get_template('header', ['is_pro' => wp_slimstat::pro_is_installed()]); ?> <img class="upgrade-pro-background" src="<?php echo esc_url(plugin_dir_url(__FILE__) . '../assets/images/pro-blur.jpg'); ?>"> </div> </div> \ No newline at end of file @@ -1,5 +1,8 @@ <?php +use SlimStat\Utils\Query; +use SlimStat\Components\DateRangeHelper; + // Let's define the main class with all the methods that we need class wp_slimstat_db { @@ -129,10 +132,58 @@ // Filters use the following format: browser equals Firefox&&&country contains gb $filters_array = []; + // Handle type parameter for date presets and custom ranges + if (isset($_GET['type'])) { + // Sanitize the type parameter to prevent XSS + $type = sanitize_key($_GET['type']); + + if ($type !== 'custom') { + // Handle preset types + // Validate that the type is a valid preset before using it + $valid_presets = ['today', 'yesterday', 'this_week', 'last_week', 'this_month', 'last_month', + 'last_7_days', 'last_28_days', 'last_30_days', 'last_90_days', + 'last_6_months', 'this_year']; + + if (in_array($type, $valid_presets, true)) { + $preset_range = DateRangeHelper::get_range_by_preset($type); + if ($preset_range) { + $filters_array['strtotime'] = 'strtotime equals ' . sanitize_text_field(wp_date('Y-m-d', $preset_range['end'])); + // Calculate days by normalizing to midnight to avoid DST issues + $start_day = strtotime(wp_date('Y-m-d', $preset_range['start'])); + $end_day = strtotime(wp_date('Y-m-d', $preset_range['end'])); + $interval_days = (($end_day - $start_day) / 86400) + 1; + $filters_array['interval'] = 'interval equals -' . absint($interval_days); + } + } + } elseif (isset($_GET['from']) && isset($_GET['to'])) { + // Sanitize date inputs to prevent XSS + $from_date = sanitize_text_field($_GET['from']); + $to_date = sanitize_text_field($_GET['to']); + + // Validate date format (YYYY-MM-DD) + if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $from_date) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $to_date)) { + // Calculate interval days directly from the date strings + $start_day = strtotime($from_date); + $end_day = strtotime($to_date); + + // Basic validation + if ($start_day && $end_day && $start_day <= $end_day) { + $interval_days = (($end_day - $start_day) / 86400) + 1; + + // Use the date strings directly without converting back and forth + $filters_array['strtotime'] = 'strtotime equals ' . $to_date; + $filters_array['interval'] = 'interval equals -' . absint($interval_days); + } + } + } + } + // Filters are set via javascript as hidden fields and submitted as a POST request. They override anything passed through the regular input fields if (!empty($_REQUEST['fs']) && is_array($_REQUEST['fs'])) { foreach ($_REQUEST['fs'] as $a_request_filter_name => $a_request_filter_value) { - $filters_array[htmlspecialchars($a_request_filter_name)] = sprintf('%s %s', $a_request_filter_name, $a_request_filter_value); + $safe_name = sanitize_text_field(wp_unslash($a_request_filter_name)); + $safe_value = str_replace('&&&', '', sanitize_text_field(wp_unslash($a_request_filter_value))); + $filters_array[$safe_name] = sprintf('%s %s', $safe_name, $safe_value); } } @@ -208,8 +259,10 @@ return ''; } - public static function get_combined_where($_where = '', $_column = '*', $_use_date_filters = true, $_slim_stats_table_alias = '') + public static function get_combined_where($_where = '', $_column = '*', $_use_date_filters = true, $_slim_stats_table_alias = '', $where_params = null) { + global $wpdb; + $dt_with_alias = 'dt'; if (!empty($_slim_stats_table_alias)) { $dt_with_alias = $_slim_stats_table_alias . '.' . $dt_with_alias; @@ -221,10 +274,20 @@ $_where = self::_get_sql_where(self::$filters_normalized['columns'], $_slim_stats_table_alias); if ($_use_date_filters) { - $time_range_condition = $dt_with_alias . ' BETWEEN ' . self::$filters_normalized['utime']['start'] . ' AND ' . self::$filters_normalized['utime']['end']; + // Use $wpdb->prepare() for all dynamic SQL values + $time_range_condition = $wpdb->prepare( + $dt_with_alias . ' BETWEEN %d AND %d', + intval(self::$filters_normalized['utime']['start']), + intval(self::$filters_normalized['utime']['end']) + ); } } elseif ($_use_date_filters) { - $time_range_condition = $dt_with_alias . ' BETWEEN ' . self::$filters_normalized['utime']['start'] . ' AND ' . self::$filters_normalized['utime']['end']; + // Use $wpdb->prepare() for all dynamic SQL values + $time_range_condition = $wpdb->prepare( + $dt_with_alias . ' BETWEEN %d AND %d', + intval(self::$filters_normalized['utime']['start']), + intval(self::$filters_normalized['utime']['end']) + ); } // This could happen if we have custom filters (add-ons, third party tools) @@ -242,7 +305,12 @@ } if ($_use_date_filters) { - $time_range_condition = $dt_with_alias . ' BETWEEN ' . self::$filters_normalized['utime']['start'] . ' AND ' . self::$filters_normalized['utime']['end']; + // Use $wpdb->prepare() for all dynamic SQL values + $time_range_condition = $wpdb->prepare( + $dt_with_alias . ' BETWEEN %d AND %d', + intval(self::$filters_normalized['utime']['start']), + intval(self::$filters_normalized['utime']['end']) + ); } } @@ -266,6 +334,12 @@ } } + // If where_param is provided and where contains %s or %d, use prepare + if (null !== $where_params && (false !== strpos($_where, '%s') || false !== strpos($_where, '%d'))) { + global $wpdb; + $_where = is_array($where_params) ? $wpdb->prepare($_where, ...$where_params) : $wpdb->prepare($_where, $where_params); + } + return $_where; } @@ -336,16 +410,17 @@ break; case 'is_greater_than': - $where[0] = '%s > ' . $column_with_alias; + $where[0] = sprintf('%s > %%s', $column_with_alias); break; case 'is_less_than': - $where[0] = '%s < ' . $column_with_alias; + $where[0] = sprintf('%s < %%s', $column_with_alias); break; case 'between': $range = explode(',', $_value); - $where = ['%s BETWEEN %d AND ' . $column_with_alias, [$range[0], $range[1]]]; + $where[0] = sprintf('%s BETWEEN %%d AND %%d', $column_with_alias); + $where[1] = [intval($range[0]), intval($range[1])]; break; case 'matches': @@ -362,12 +437,33 @@ } if (isset($where[1]) && '' != $where[1]) { + // Handle array of values for operators like 'between' + if (is_array($where[1])) { + return $GLOBALS['wpdb']->prepare($where[0], ...$where[1]); + } return $GLOBALS['wpdb']->prepare($where[0], $where[1]); } else { return $where[0]; } } + /** + * Helper to enable caching on a Query object if the date range does not include today. + * + * @param Query $query + */ + protected static function maybe_enable_query_cache($query) + { + // Use the end date from normalized filters (if available) + if (!empty(self::$filters_normalized['utime']['end'])) { + // Convert to Y-m-d for comparison (Query expects string date) + $to = wp_date('Y-m-d', self::$filters_normalized['utime']['end']); + if (method_exists($query, 'canUseCacheForDateRange')) { + $query->canUseCacheForDateRange($to); + } + } + } + public static function get_results($_sql = '', $_select_no_aggregate_values = '', $_order_by = '', $_group_by = '', $_aggregate_values_add = '') { $_sql = apply_filters('slimstat_get_results_sql', $_sql, $_select_no_aggregate_values, $_order_by, $_group_by, $_aggregate_values_add); @@ -376,15 +472,63 @@ self::$debug_message .= sprintf("<p class='debug'>%s</p>", $_sql); } - $cached_results = wp_cache_get(md5($_sql), 'wp-slimstat'); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $sql_trim = ltrim($_sql); + + // Try to convert SQL to Query class for better caching and performance + if (0 === stripos($sql_trim, 'select') && false !== stripos($sql_trim, $table)) { + // Add caching for SELECT queries + $cache_key = 'slimstat_query_' . md5($_sql); + $cached_results = get_transient($cache_key); + if (false !== $cached_results) { + return $cached_results; + } + + // Try to parse and convert to Query class + if (preg_match('/SELECT (.+) FROM [^ ]+ WHERE (.+?)( GROUP BY (.+?))?( ORDER BY (.+?))?( LIMIT (\d+), (\d+))?/is', $_sql, $m)) { + $columns = trim($m[1]); + $where = trim($m[2]); + $group_by = isset($m[4]) ? trim($m[4]) : ''; + $order_by = isset($m[6]) ? trim($m[6]) : ''; + $limit_offset = isset($m[8]) ? intval($m[8]) : 0; + $limit_count = isset($m[9]) ? intval($m[9]) : 100; + + $q = Query::select($columns)->from($table); + + if ($where && '1=1' !== $where) { + $q->whereRaw($where); + } + + if ('' !== $group_by && '0' !== $group_by) { + $q->groupBy($group_by); + } + + if ('' !== $order_by && '0' !== $order_by) { + $q->orderBy($order_by); + } - // Save the results of this query in our object cache - if (empty($cached_results)) { - $cached_results = wp_slimstat::$wpdb->get_results($_sql, ARRAY_A); - wp_cache_add(md5($_sql), $cached_results, 'wp-slimstat'); + $page = ($limit_offset / $limit_count) + 1; + $q->perPage($page, $limit_count); + $q->allowCaching(true); + $result = $q->getAll(); + set_transient($cache_key, $result, 10 * MINUTE_IN_SECONDS); + return $result; + } } - return $cached_results; + // Fallback to direct wpdb for complex queries + return $GLOBALS['wpdb']->get_results($_sql, ARRAY_A); + } + + protected static function is_simple_count_query($sql) + { + $sql_trim = ltrim($sql); + if (preg_match('/^select\s+count\s*\(.*\)\s+as\s+[a-z_][a-z0-9_]*\s+from\s+[`\w]+/i', $sql_trim) && (false === stripos($sql_trim, ' join ') && false === stripos($sql_trim, ' group by ') && false === stripos($sql_trim, ' having ') && false === stripos($sql_trim, ' union ') && false === stripos($sql_trim, ' as sub') && stripos($sql_trim, '(') === stripos($sql_trim, 'count('))) { + // no subquery before count + return true; + } + + return preg_match('/^select\s+count\s*\(\s*distinct\s+.*\)\s+as\s+[a-z_][a-z0-9_]*\s+from\s+[`\w]+/i', $sql_trim) && (false === stripos($sql_trim, ' join ') && false === stripos($sql_trim, ' group by ') && false === stripos($sql_trim, ' having ') && false === stripos($sql_trim, ' union ') && false === stripos($sql_trim, ' as sub')); } public static function get_var($_sql = '', $_aggregate_value = '') @@ -395,12 +539,52 @@ self::$debug_message .= sprintf("<p class='debug'>%s</p>", $_sql); } - // Save the results of this query in our object cache - if (empty(wp_cache_get(md5($_sql), 'wp-slimstat'))) { - wp_cache_add(md5($_sql), wp_slimstat::$wpdb->get_var($_sql), 'wp-slimstat'); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $sql_trim = ltrim($_sql); + + // Try to convert to Query class for better performance + if (0 === stripos($sql_trim, 'select') && false !== stripos($sql_trim, $table)) { + // Parse simple count queries + if (preg_match('/^SELECT\s+COUNT\s*\(\s*(\*|DISTINCT\s+(\w+))\s*\)\s+AS\s+(\w+)\s+FROM\s+[`\w]+(?:\s+WHERE\s+(.+?))?$/i', $sql_trim, $matches)) { + $count_field = $matches[1]; + $alias = $matches[3]; + $where_clause = isset($matches[4]) ? trim($matches[4]) : ''; + + $query = Query::select($count_field)->from($table); + + if ($where_clause !== '' && $where_clause !== '0') { + $query->whereRaw($where_clause); + } + + $query->allowCaching(true); + return $query->getVar(); + } + + // Parse other aggregate queries + if (preg_match('/^SELECT\s+(\w+\([^)]+\))\s+AS\s+(\w+)\s+FROM\s+[`\w]+(?:\s+WHERE\s+(.+?))?$/i', $sql_trim, $matches)) { + $aggregate = $matches[1]; + $alias = $matches[2]; + $where_clause = isset($matches[3]) ? trim($matches[3]) : ''; + + $query = Query::select($aggregate)->from($table); + + if ($where_clause !== '' && $where_clause !== '0') { + $query->whereRaw($where_clause); + } + + $query->allowCaching(true); + return $query->getVar(); + } } - return wp_cache_get(md5($_sql), 'wp-slimstat'); + // Fallback to wpdb for complex queries + if (0 === stripos(trim($_sql), 'select')) { + $query = Query::select('*')->from('(' . $_sql . ') as sub'); + self::maybe_enable_query_cache($query); + return $query->getVar(); + } else { + return wp_slimstat::$wpdb->get_var($_sql); + } } public static function parse_filters($_filters_raw) @@ -443,23 +627,23 @@ self::toggle_date_i18n_filters(false); switch ($a_filter[1]) { case 'minute': - $filters_parsed['date']['minute'] = intval(date('i', strtotime($a_filter[3], date_i18n('U')))); + $filters_parsed['date']['minute'] = intval(wp_date('i', strtotime($a_filter[3], date_i18n('U')))); break; case 'hour': - $filters_parsed['date']['hour'] = intval(date('H', strtotime($a_filter[3], date_i18n('U')))); + $filters_parsed['date']['hour'] = intval(wp_date('H', strtotime($a_filter[3], date_i18n('U')))); break; case 'day': - $filters_parsed['date']['day'] = intval(date('j', strtotime($a_filter[3], date_i18n('U')))); + $filters_parsed['date']['day'] = intval(wp_date('j', strtotime($a_filter[3], date_i18n('U')))); break; case 'month': - $filters_parsed['date']['month'] = intval(date('n', strtotime($a_filter[3], date_i18n('U')))); + $filters_parsed['date']['month'] = intval(wp_date('n', strtotime($a_filter[3], date_i18n('U')))); break; case 'year': - $filters_parsed['date']['year'] = intval(date('Y', strtotime($a_filter[3], date_i18n('U')))); + $filters_parsed['date']['year'] = intval(wp_date('Y', strtotime($a_filter[3], date_i18n('U')))); break; default: @@ -495,7 +679,11 @@ // no break here: if value IS numeric, go to the default parser here below default: - $filters_parsed['columns'][$a_filter[1]] = [$a_filter[2], isset($a_filter[3]) ? str_replace('\\', '', htmlspecialchars_decode($a_filter[3])) : '']; + $filter_value = isset($a_filter[3]) ? str_replace('\\', '', htmlspecialchars_decode($a_filter[3])) : ''; + // Only add filter if value is not empty (ignore filters without values) + if (trim($filter_value) !== '') { + $filters_parsed['columns'][$a_filter[1]] = [$a_filter[2], $filter_value]; + } break; } } @@ -588,6 +776,7 @@ $fn['utime']['end'] = intval(date_i18n('U')); } + // Add 1 second to account for the time difference between midnight and 23:59:59 $fn['utime']['range'] += 1; $fn['utime']['start'] = $fn['utime']['end'] + $fn['utime']['range']; @@ -659,41 +848,70 @@ )); } - public static function count_records($_column = 'id', $_where = '', $_use_date_filters = true) + public static function count_records($_column = 'id', $_where = '', $_use_date_filters = true, $where_params = []) { // Validating the column if (false === in_array($_column, ['id', 'ip', 'other_ip', 'username', 'email', 'country', 'location', 'city', 'referer', 'resource', 'searchterms', 'notes', 'visit_id', 'server_latency', 'page_performance', 'browser', 'browser_version', 'browser_type', 'platform', 'language', 'fingerprint', 'user_agent', 'resolution', 'screen_width', 'screen_height', 'content_type', 'category', 'author', 'content_id', 'outbound_resource', 'tz_offset', 'dt_out', 'dt'])) { return null; } + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; $distinct_column = ('id' != $_column) ? 'DISTINCT ' . $_column : $_column; - $_where = self::get_combined_where($_where, $_column, $_use_date_filters); - return intval(self::get_var( - " - SELECT COUNT({$distinct_column}) counthits - FROM {$GLOBALS['wpdb']->prefix}slim_stats - WHERE {$_where}", - 'SUM(counthits) AS counthits' - )); + $query = Query::select(sprintf('COUNT(%s) as counthits', $distinct_column))->from($table); + + // Add date filters if needed + if ($_use_date_filters && !empty(self::$filters_normalized['utime']['start']) && !empty(self::$filters_normalized['utime']['end'])) { + $query->where('dt', 'BETWEEN', [intval(self::$filters_normalized['utime']['start']), intval(self::$filters_normalized['utime']['end'])]); + } + + if ( + !empty($_where) + && !empty($where_params) + && (false !== strpos($_where, '%s') || false !== strpos($_where, '%d') || false !== strpos($_where, '%f')) + ) { + $_where = is_array($where_params) ? $GLOBALS['wpdb']->prepare($_where, ...$where_params) : $GLOBALS['wpdb']->prepare($_where, $where_params); + } + + // Add custom where clause + if (!empty($_where)) { + $query->whereRaw($_where); + } + + // Add other filters + if (!empty(self::$filters_normalized['columns'])) { + $where_clause = self::_get_sql_where(self::$filters_normalized['columns']); + if (!empty($where_clause)) { + $query->whereRaw($where_clause); + } + } + + $query->allowCaching(true); + return intval($query->getVar()); } public static function count_records_having($_column = 'id', $_where = '', $_having = '') { - $distinct_column = ('id' != $_column) ? 'DISTINCT ' . $_column : $_column; - $_where = self::get_combined_where($_where, $_column); + // Allowlist: only known schema columns are allowed as identifiers + $allowed_columns = array_keys(self::$columns_names); + if (!in_array($_column, $allowed_columns, true)) { + return 0; + } + + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $distinct_column = ('id' !== $_column) ? 'DISTINCT ' . esc_sql($_column) : esc_sql($_column); + + $query = Query::select("COUNT(*) as counthits") + ->from("( + SELECT {$distinct_column} + FROM {$table} + WHERE " . self::get_combined_where($_where, $_column) . " + GROUP BY " . esc_sql($_column) . " + HAVING {$_having} + ) AS ts1"); - return intval(self::get_var( - " - SELECT COUNT(*) counthits FROM ( - SELECT {$distinct_column} - FROM {$GLOBALS['wpdb']->prefix}slim_stats - WHERE {$_where} - GROUP BY {$_column} - HAVING {$_having} - ) AS ts1", - 'SUM(counthits) AS counthits' - )); + $query->allowCaching(true); + return intval($query->getVar()); } public static function get_data_size() @@ -715,8 +933,6 @@ public static function get_group_by($_args = []) { - $where = self::get_combined_where(); - if (empty($_args['column_group'])) { $_args['column_group'] = 'id'; } @@ -725,51 +941,65 @@ $_args['group_by'] = 'id'; } - // prepare the query - $sql = $GLOBALS['wpdb']->prepare( - " - SELECT {$_args[ 'group_by' ]}, COUNT(*) AS counthits, GROUP_CONCAT( DISTINCT {$_args[ 'column_group' ]} SEPARATOR ';;;' ) as column_group - FROM {$GLOBALS['wpdb']->prefix}slim_stats - WHERE {$where} AND {$_args[ 'group_by' ]} IS NOT NULL - GROUP BY {$_args[ 'group_by' ]} - - ORDER BY counthits DESC - LIMIT %d, %d", - self::$filters_normalized['misc']['start_from'], - self::$filters_normalized['misc']['limit_results'] - ); - return self::get_results($sql, $_args['group_by'], $_args['group_by'] . ' ASC'); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $query = Query::select([ + $_args['group_by'], + 'COUNT(*) AS counthits', + sprintf("GROUP_CONCAT( DISTINCT %s SEPARATOR ';;;' ) as column_group", $_args['column_group']) + ])->from($table); + + // Add date filters if needed + if (!empty(self::$filters_normalized['utime']['start']) && !empty(self::$filters_normalized['utime']['end'])) { + $query->where('dt', 'BETWEEN', [intval(self::$filters_normalized['utime']['start']), intval(self::$filters_normalized['utime']['end'])]); + } + + // Add other filters + if (!empty(self::$filters_normalized['columns'])) { + $where_clause = self::_get_sql_where(self::$filters_normalized['columns']); + if (!empty($where_clause)) { + $query->whereRaw($where_clause); + } + } + + // Add IS NOT NULL condition + $query->where($_args['group_by'], 'IS NOT', null); + + // GROUP BY + $query->groupBy($_args['group_by']); + + // ORDER BY + $query->orderBy('counthits DESC'); + + // LIMIT + $start_from = intval(self::$filters_normalized['misc']['start_from']); + $limit_results = intval(self::$filters_normalized['misc']['limit_results']); + $page = ($start_from / $limit_results) + 1; + $query->perPage($page, $limit_results); + + $query->allowCaching(true); + return $query->getAll(); } public static function get_max_and_average_pages_per_visit() { $where = self::get_combined_where('visit_id > 0'); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; - return self::get_results( - " - SELECT AVG(ts1.counthits) AS avghits, MAX(ts1.counthits) AS maxhits FROM ( - SELECT count(ip) counthits, visit_id - FROM {$GLOBALS['wpdb']->prefix}slim_stats - WHERE {$where} - GROUP BY visit_id - ) AS ts1", - 'blog_id', - '', - '', - 'AVG(avghits) AS avghits, MAX(maxhits) AS maxhits' - ); + $subQuery = sprintf('SELECT count(ip) counthits, visit_id FROM %s WHERE %s GROUP BY visit_id', $table, $where); + + $query = Query::select('AVG(ts1.counthits) AS avghits, MAX(ts1.counthits) AS maxhits') + ->from(sprintf('(%s) AS ts1', $subQuery)); + + self::maybe_enable_query_cache($query); + return $query->getAll(); } public static function get_oldest_visit() { - return self::get_var( - " - SELECT dt - FROM {$GLOBALS['wpdb']->prefix}slim_stats - ORDER BY dt ASC - LIMIT 0, 1", - 'MIN(dt)' - ); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $query = Query::select('dt')->from($table)->orderBy('dt', 'ASC')->limit(1); + $query->allowCaching(true, DAY_IN_SECONDS); + return $query->getVar(); } public static function get_overview_summary() @@ -823,8 +1053,6 @@ public static function get_recent($_column = 'id', $_where = '', $_having = '', $_use_date_filters = true, $_as_column = '', $_more_columns = '', $_order_by = 'dt DESC') { - global $wpdb; - // This function can be passed individual arguments, or an array of arguments if (is_array($_column)) { $_where = empty($_column['where']) ? '' : $_column['where']; $_having = empty($_column['having']) ? '' : $_column['having']; @@ -835,54 +1063,67 @@ $_column = $_column['columns']; } - $columns = $_column; + $columns = ('*' === $_column) + ? ['id', 'ip', 'dt', 'username', 'referer', 'resource', 'browser', 'platform', 'country', 'city', 'content_type', 'notes', 'visit_id', 'server_latency', 'page_performance', 'browser_version', 'browser_type', 'language', 'fingerprint', 'user_agent', 'resolution', 'screen_width', 'screen_height', 'category', 'author', 'content_id', 'outbound_resource', 'tz_offset', 'dt_out'] + : array_map('trim', explode(',', $_column)); if (!empty($_as_column)) { - $columns = sprintf('%s AS %s', $_column, $_as_column); + $columns[0] = $columns[0] . ' AS ' . $_as_column; } - // Add the IP column, used to display details about that visit - if ('ip' != $_column) { - $columns .= ', ip'; + if (!empty($_more_columns)) { + $more_cols = array_map('trim', explode(',', $_more_columns)); + $columns = array_merge($columns, $more_cols); } - if ('*' != $_column) { - $columns .= ', dt'; - $group_by = 'GROUP BY ' . $_column; - } else { - $columns = 'id, ip, other_ip, username, email, country, city, location, referer, resource, searchterms, notes, visit_id, server_latency, page_performance, browser, browser_version, browser_type, platform, language, fingerprint, user_agent, resolution, screen_width, screen_height, content_type, category, author, content_id, outbound_resource, tz_offset, dt_out, dt'; - $group_by = ''; + if (!in_array('dt', $columns)) { + $columns[] = 'dt'; } - if (!empty($_more_columns)) { - $columns .= ', ' . $_more_columns; + if (!in_array('ip', $columns)) { + $columns[] = 'ip'; } - $_where = self::get_combined_where($_where, $_column, $_use_date_filters); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $query = Query::select(implode(', ', $columns))->from($table); + + // Always add date filter as a proper where() clause so placeholders are replaced + if ($_use_date_filters && !empty(self::$filters_normalized['utime']['start']) && !empty(self::$filters_normalized['utime']['end']) && !$query->hasWhereClause('dt', 'BETWEEN')) { + $query->where('dt', 'BETWEEN', [intval(self::$filters_normalized['utime']['start']), intval(self::$filters_normalized['utime']['end'])]); + } + + // Apply active column filters (e.g., browser equals Chrome) using the existing normalization logic + if (!empty(self::$filters_normalized['columns'])) { + $normalized_where = self::_get_sql_where(self::$filters_normalized['columns']); + if (!empty($normalized_where)) { + $query->whereRaw($normalized_where); + } + } + + // Only add additional non-parameterized conditions passed via $_where + if (!empty($_where)) { + $query->whereRaw($_where); + } + + // HAVING + if (!empty($_having)) { + $query->havingRaw($_having); + } - // Sanitize and protect WHERE clause - if (false !== strpos($_where, 'OR') && false === strpos($_where, '(')) { - $_where = '(' . $_where . ')'; + // ORDER BY + if (!empty($_order_by)) { + $query->orderBy($_order_by); } + // LIMIT $start = max(0, intval(self::$filters_normalized['misc']['start_from'])); $limit = max(1, intval(self::$filters_normalized['misc']['limit_results'])); + $query->limit(sprintf('%d OFFSET %d', $limit, $start)); - // Prepare the query - $sql = $wpdb->prepare( - " - SELECT {$columns} - FROM {$wpdb->prefix}slim_stats - WHERE [[_WHERE_]] - {$group_by} - ORDER BY {$_order_by} - LIMIT %d, %d", - $start, - $limit - ); + $query->allowCaching(false); - $sql = str_replace('[[_WHERE_]]', $_where, $sql); - return self::get_results($sql, $columns, 'dt DESC'); + + return $query->getAll(); } public static function get_recent_events() @@ -904,10 +1145,9 @@ $clean_outbound_resources = []; foreach ($mixed_outbound_resources as $a_mixed_resource) { - $exploded_resources = explode(';;;', $a_mixed_resource['outbound_resource']); + $exploded_resources = explode(';;;', $a_mixed_resource['outbound_resource'] ?? ''); foreach ($exploded_resources as $a_exploded_resource) { - $a_mixed_resource['outbound_resource'] = $a_exploded_resource; - $clean_outbound_resources[] = $a_mixed_resource; + $clean_outbound_resources[] = $a_exploded_resource; } } @@ -916,11 +1156,19 @@ public static function get_top($_column = 'id', $_where = '', $_having = '', $_use_date_filters = true, $_as_column = '') { - global $wpdb; - // This function can be passed individual arguments, or an array of arguments if (is_array($_column)) { - $_where = empty($_column['where']) ? '' : $_column['where']; + $where_params = !empty($_column['where_params']) ? $_column['where_params'] : []; + $_where = !empty($_column['where']) ? $_column['where'] : ''; + + if ( + !empty($_where) + && !empty($where_params) + && (false !== strpos($_where, '%s') || false !== strpos($_where, '%d') || false !== strpos($_where, '%f')) + ) { + $_where = is_array($where_params) ? $GLOBALS['wpdb']->prepare($_where, ...$where_params) : $GLOBALS['wpdb']->prepare($_where, $where_params); + } + $_having = empty($_column['having']) ? '' : $_column['having']; $_use_date_filters = empty($_column['use_date_filters']) ? true : $_column['use_date_filters']; $_as_column = empty($_column['as_column']) ? '' : $_column['as_column']; @@ -935,36 +1183,46 @@ $_as_column = $_column; } - $_where = self::get_combined_where($_where, $_as_column, $_use_date_filters); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $query = Query::select([$_column, 'COUNT(*) AS counthits'])->from($table); - $column = $_column; - $where_clause = $_where; - $group_by = $group_by_column; - $having_clause = $_having; - $start_from = intval(self::$filters_normalized['misc']['start_from']); - $limit_results = intval(self::$filters_normalized['misc']['limit_results']); + // Add date filters if needed + if ($_use_date_filters && !empty(self::$filters_normalized['utime']['start']) && !empty(self::$filters_normalized['utime']['end'])) { + $query->where('dt', 'BETWEEN', [intval(self::$filters_normalized['utime']['start']), intval(self::$filters_normalized['utime']['end'])]); + } - $sql = " - SELECT {$column}, COUNT(*) AS counthits - FROM {$wpdb->prefix}slim_stats - WHERE [[_WHERE_]] - GROUP BY {$group_by} - {$having_clause} - ORDER BY counthits DESC - LIMIT %d, %d - "; + // Add custom where clause + if (!empty($_where)) { + $query->whereRaw($_where); + } - $prepared_sql = $wpdb->prepare($sql, $start_from, $limit_results); + // Add other filters + if (!empty(self::$filters_normalized['columns'])) { + $where_clause = self::_get_sql_where(self::$filters_normalized['columns']); + if (!empty($where_clause)) { + $query->whereRaw($where_clause); + } + } - $prepared_sql = str_replace('[[_WHERE_]]', $where_clause, $prepared_sql); + // GROUP BY + $query->groupBy($group_by_column); - return self::get_results( - $prepared_sql, - (!empty($_as_column) && $_as_column != $_column) ? $_as_column : $_column, - 'counthits DESC', - (!empty($_as_column) && $_as_column != $_column) ? $_as_column : $_column, - 'SUM(counthits) AS counthits' - ); + // HAVING + if (!empty($_having)) { + $query->havingRaw($_having); + } + + // ORDER BY + $query->orderBy('counthits DESC'); + + // LIMIT + $start_from = intval(self::$filters_normalized['misc']['start_from']); + $limit_results = intval(self::$filters_normalized['misc']['limit_results']); + $page = ($start_from / $limit_results) + 1; + $query->perPage($page, $limit_results); + + $query->allowCaching(true); + return $query->getAll(); } public static function get_top_aggr($_column = 'id', $_where = '', $_outer_select_column = '', $_aggr_function = 'MAX') @@ -986,48 +1244,41 @@ } $_where = self::get_combined_where($_where, $_column); + $table = $GLOBALS['wpdb']->prefix . 'slim_stats'; - // prepare the query - $sql = $GLOBALS['wpdb']->prepare( - " - SELECT {$_outer_select_column}, ts1.aggrid as {$_column}, COUNT(*) counthits - FROM ( - SELECT {$_column}, {$_aggr_function}(id) aggrid - FROM {$GLOBALS['wpdb']->prefix}slim_stats - WHERE {$_where} - GROUP BY {$_column} - ) AS ts1 JOIN {$GLOBALS['wpdb']->prefix}slim_stats t1 ON ts1.aggrid = t1.id - GROUP BY {$_outer_select_column} - ORDER BY counthits DESC - LIMIT %d, %d", - self::$filters_normalized['misc']['start_from'], - self::$filters_normalized['misc']['limit_results'] - ); - return self::get_results($sql, $_outer_select_column, 'counthits DESC', $_outer_select_column, $_aggr_function . '(aggrid), SUM(counthits)'); + $subQuerySql = sprintf('SELECT %s, %s(id) as aggrid FROM %s WHERE %s GROUP BY %s', $_column, $_aggr_function, $table, $_where, $_column); + + $query = Query::select(sprintf('%s, ts1.aggrid as %s, COUNT(*) as counthits', $_outer_select_column, $_column)) + ->from(sprintf('(%s) AS ts1', $subQuerySql)) + ->join($table . ' t1', 'ts1.aggrid', 't1.id') + ->groupBy($_outer_select_column) + ->orderBy('counthits DESC') + ->perPage(self::$filters_normalized['misc']['start_from'], self::$filters_normalized['misc']['limit_results']); + + self::maybe_enable_query_cache($query); + return $query->getAll(); } public static function get_top_events() { + $table_events = $GLOBALS['wpdb']->prefix . 'slim_events'; + $table_stats = $GLOBALS['wpdb']->prefix . 'slim_stats'; + if (empty(self::$filters_normalized['columns'])) { - $from = $GLOBALS['wpdb']->prefix . 'slim_events te'; - $where = wp_slimstat_db::get_combined_where('notes NOT LIKE "type:click%"', 'notes'); + $query = Query::select('te.notes, COUNT(*) as counthits') + ->from($table_events . ' te') + ->whereRaw(wp_slimstat_db::get_combined_where('notes NOT LIKE "type:click%"', 'notes')); } else { - $from = sprintf('%sslim_events te INNER JOIN %sslim_stats t1 ON te.id = t1.id', $GLOBALS['wpdb']->prefix, $GLOBALS['wpdb']->prefix); - $where = wp_slimstat_db::get_combined_where('te.notes NOT LIKE "_ype:click%"', 'te.notes', true, 't1'); + $query = Query::select('te.notes, COUNT(*) as counthits') + ->from($table_events . ' te') + ->join($table_stats . ' t1', 'te.id', 't1.id') + ->whereRaw(wp_slimstat_db::get_combined_where('te.notes NOT LIKE "_ype:click%"', 'te.notes', true, 't1')); } - return self::get_results( - " - SELECT te.notes, COUNT(*) counthits - FROM {$from} - WHERE {$where} - GROUP BY te.notes - ORDER BY counthits DESC", - 'notes', - 'counthits DESC', - 'notes', - 'SUM(counthits) AS counthits' - ); + $query->groupBy('te.notes')->orderBy('counthits DESC'); + + self::maybe_enable_query_cache($query); + return $query->getAll(); } public static function get_top_outbound() @@ -1036,7 +1287,7 @@ $clean_outbound_resources = []; foreach ($mixed_outbound_resources as $a_mixed_resource) { - $exploded_resources = explode(';;;', $a_mixed_resource['outbound_resource']); + $exploded_resources = explode(';;;', $a_mixed_resource['outbound_resource'] ?? ''); foreach ($exploded_resources as $a_exploded_resource) { $clean_outbound_resources[] = $a_exploded_resource; } @@ -1073,7 +1324,7 @@ $results[0]['tooltip'] = __('A pageview is a request to load a single HTML page on your website.', 'wp-slimstat'); $results[1]['metric'] = __('Unique Referrers', 'wp-slimstat'); - $results[1]['value'] = number_format_i18n(wp_slimstat_db::count_records('referer', sprintf("referer NOT LIKE '%%%s%%'", $server_name))); + $results[1]['value'] = number_format_i18n(wp_slimstat_db::count_records('referer', 'referer NOT LIKE %s', true, ['%' . $GLOBALS['wpdb']->esc_like($server_name) . '%'])); $results[1]['tooltip'] = __('A referrer (or referring site) is a site that a visitor previously visited before following a link to your site.', 'wp-slimstat'); $results[2]['metric'] = __('Direct Pageviews', 'wp-slimstat'); @@ -1081,7 +1332,7 @@ $results[2]['tooltip'] = __("Visitors who typed your website URL directly into their browser address bar. It can also refer to visitors who clicked on one of their bookmarked links, untagged links within emails, or links in documents that don't include tracking variables.", 'wp-slimstat'); $results[3]['metric'] = __('From External SERP', 'wp-slimstat'); - $results[3]['value'] = number_format_i18n(wp_slimstat_db::count_records('id', "searchterms IS NOT NULL AND referer IS NOT NULL AND referer NOT LIKE '%" . home_url() . "%'")); + $results[3]['value'] = number_format_i18n(wp_slimstat_db::count_records('id', 'searchterms IS NOT NULL AND referer IS NOT NULL AND referer NOT LIKE %s', true, ['%' . $GLOBALS['wpdb']->esc_like(home_url()) . '%'])); $results[3]['tooltip'] = __('Visitors who clicked on a link to your website listed on a search engine result page (SERP). This metric only counts visits coming from EXTERNAL search pages.', 'wp-slimstat'); $results[4]['metric'] = __('Unique Landing Pages', 'wp-slimstat'); @@ -1097,7 +1348,7 @@ $results[6]['tooltip'] = __('Percentage of single-page visits, i.e. visits in which the person left your site from the entrance page.', 'wp-slimstat'); $results[7]['metric'] = __('Currently from search engines', 'wp-slimstat'); - $results[7]['value'] = number_format_i18n(wp_slimstat_db::count_records('id', "searchterms IS NOT NULL AND referer IS NOT NULL AND referer NOT LIKE '%" . home_url() . "%' AND dt > UNIX_TIMESTAMP() - 300", false)); + $results[7]['value'] = number_format_i18n(wp_slimstat_db::count_records('id', 'searchterms IS NOT NULL AND referer IS NOT NULL AND referer NOT LIKE %s AND dt > UNIX_TIMESTAMP() - 300', false, ['%' . $GLOBALS['wpdb']->esc_like(home_url()) . '%'])); $results[7]['tooltip'] = __('Visitors who clicked on a link to your website listed on a search engine result page (SERP), tracked in the last 5 minutes.', 'wp-slimstat'); return $results; @@ -1215,32 +1466,35 @@ public static function get_your_blog() { if (false === ($results = get_transient('slimstat_your_content'))) { - $results = []; + $results = []; + $posts_table = $GLOBALS['wpdb']->posts; + $comments_table = $GLOBALS['wpdb']->comments; + $slim_stats_table = $GLOBALS['wpdb']->prefix . 'slim_stats'; $results[0]['metric'] = __('Content Items', 'wp-slimstat'); - $results[0]['value'] = number_format_i18n($GLOBALS['wpdb']->get_var(sprintf("SELECT COUNT(*) FROM %s WHERE post_type <> 'revision' AND post_status <> 'auto-draft'", $GLOBALS['wpdb']->posts))); + $results[0]['value'] = number_format_i18n(Query::select('COUNT(*)')->from($posts_table)->where('post_type', '!=', 'revision')->where('post_status', '!=', 'auto-draft')->getVar()); $results[0]['tooltip'] = __('This value includes not only posts and pages, but any custom post type, regardless of their status.', 'wp-slimstat'); $results[1]['metric'] = __('Posts', 'wp-slimstat'); - $results[1]['value'] = $GLOBALS['wpdb']->get_var(sprintf("SELECT COUNT(*) FROM %s WHERE post_type = 'post'", $GLOBALS['wpdb']->posts)); + $results[1]['value'] = Query::select('COUNT(*)')->from($posts_table)->where('post_type', '=', 'post')->getVar(); $results[2]['metric'] = __('Pages', 'wp-slimstat'); - $results[2]['value'] = number_format_i18n($GLOBALS['wpdb']->get_var(sprintf("SELECT COUNT(*) FROM %s WHERE post_type = 'page'", $GLOBALS['wpdb']->posts))); + $results[2]['value'] = number_format_i18n(Query::select('COUNT(*)')->from($posts_table)->where('post_type', '=', 'page')->getVar()); $results[3]['metric'] = __('Attachments', 'wp-slimstat'); - $results[3]['value'] = number_format_i18n($GLOBALS['wpdb']->get_var(sprintf("SELECT COUNT(*) FROM %s WHERE post_type = 'attachment'", $GLOBALS['wpdb']->posts))); + $results[3]['value'] = number_format_i18n(Query::select('COUNT(*)')->from($posts_table)->where('post_type', '=', 'attachment')->getVar()); $results[4]['metric'] = __('Revisions', 'wp-slimstat'); - $results[4]['value'] = number_format_i18n($GLOBALS['wpdb']->get_var(sprintf("SELECT COUNT(*) FROM %s WHERE post_type = 'revision'", $GLOBALS['wpdb']->posts))); + $results[4]['value'] = number_format_i18n(Query::select('COUNT(*)')->from($posts_table)->where('post_type', '=', 'revision')->getVar()); $results[5]['metric'] = __('Comments', 'wp-slimstat'); - $results[5]['value'] = $GLOBALS['wpdb']->get_var('SELECT COUNT( * ) FROM ' . $GLOBALS[ 'wpdb' ]->comments); + $results[5]['value'] = Query::select('COUNT(*)')->from($comments_table)->getVar(); $results[6]['metric'] = __('Avg Comments per Post', 'wp-slimstat'); $results[6]['value'] = empty($results[1]['value']) ? 0 : number_format_i18n($results[5]['value'] / $results[1]['value']); $results[7]['metric'] = __('Avg Server Latency', 'wp-slimstat'); - $results[7]['value'] = number_format_i18n(wp_slimstat::$wpdb->get_var(sprintf('SELECT AVG(server_latency) FROM %sslim_stats WHERE server_latency <> 0', $GLOBALS[ 'wpdb' ]->prefix))); + $results[7]['value'] = number_format_i18n(Query::select('AVG(server_latency)')->from($slim_stats_table)->where('server_latency', '!=', 0)->getVar()); $results[7]['tooltip'] = __('Latency is the amount of time it takes for the host server to receive and process a request for a page object. The amount of latency depends largely on how far away the user is from the server.', 'wp-slimstat'); $results[1]['value'] = number_format_i18n($results[1]['value']); @@ -122,7 +122,7 @@ ], 'chart_labels' => [ __('Pageviews', 'wp-slimstat'), - __('Unique IPs', 'wp-slimstat'), + (('on' == (wp_slimstat::$settings['hash_ip'] ?? 'off')) ? __('Unique Visitors', 'wp-slimstat') : __('Unique IPs', 'wp-slimstat')), ], ], 'classes' => ['extralarge', 'chart'], @@ -144,7 +144,7 @@ 'callback_args' => [ 'type' => 'recent', 'columns' => 'ip', - 'where' => '(dt_out > ' . (date_i18n('U') - 300) . ' OR dt > ' . (date_i18n('U') - 300) . ')', + 'where' => '(dt_out > ' . (date_i18n('U') - 300) . ') OR (dt > ' . (date_i18n('U') - 300) . ')', 'use_date_filters' => false, 'raw' => ['wp_slimstat_db', 'get_recent'], ], @@ -181,12 +181,13 @@ 'title' => __('Top Referring Domains', 'wp-slimstat'), 'callback' => [self::class, 'raw_results_to_html'], 'callback_args' => [ - 'type' => 'top', - 'columns' => 'REPLACE( SUBSTRING_INDEX( ( SUBSTRING_INDEX( ( SUBSTRING_INDEX( referer, "://", -1 ) ), "/", 1 ) ), ".", -5 ), "www.", "" )', - 'as_column' => 'referer', - 'filter_op' => 'contains', - 'where' => 'referer NOT LIKE "%' . str_replace('www.', '', parse_url(home_url(), PHP_URL_HOST)) . '%"', - 'raw' => ['wp_slimstat_db', 'get_top'], + 'type' => 'top', + 'columns' => 'REPLACE( SUBSTRING_INDEX( ( SUBSTRING_INDEX( ( SUBSTRING_INDEX( referer, "://", -1 ) ), "/", 1 ) ), ".", -5 ), "www.", "" )', + 'as_column' => 'referer', + 'filter_op' => 'contains', + 'where' => 'referer NOT LIKE %s', + 'where_params' => ['%' . str_replace('www.', '', parse_url(home_url(), PHP_URL_HOST)) . '%'], + 'raw' => ['wp_slimstat_db', 'get_top'], ], 'classes' => ['normal'], 'locations' => ['slimview2', 'slimview5', 'dashboard'], @@ -254,9 +255,10 @@ 'title' => __('Users Currently Online', 'wp-slimstat'), 'callback' => [self::class, 'raw_results_to_html'], 'callback_args' => [ - 'type' => 'recent', - 'columns' => 'username', - 'where' => 'dt_out > ' . (date_i18n('U') - 300) . ' OR dt > ' . (date_i18n('U') - 300), + 'type' => 'recent', + 'columns' => 'username', + // Group OR conditions explicitly to help MySQL use indexes effectively + 'where' => '(dt_out > ' . (date_i18n('U') - 300) . ') OR (dt > ' . (date_i18n('U') - 300) . ')', 'use_date_filters' => false, 'raw' => ['wp_slimstat_db', 'get_recent'], ], @@ -599,10 +601,11 @@ 'title' => __('Recent Feeds', 'wp-slimstat'), 'callback' => [self::class, 'raw_results_to_html'], 'callback_args' => [ - 'type' => 'recent', - 'columns' => 'resource', - 'where' => '(resource LIKE "%/feed%" OR resource LIKE "%?feed=>%" OR resource LIKE "%&feed=>%" OR content_type LIKE "%feed%")', - 'raw' => ['wp_slimstat_db', 'get_recent'], + 'type' => 'recent', + 'columns' => 'resource', + 'where' => '(resource LIKE %s OR resource LIKE %s OR resource LIKE %s OR content_type LIKE %s)', + 'where_params' => ['%/feed%', '%?feed=>%', '%&feed=>%', '%feed%'], + 'raw' => ['wp_slimstat_db', 'get_recent'], ], 'classes' => ['normal'], 'locations' => ['inactive'], @@ -623,10 +626,11 @@ 'title' => __('Recent Internal Searches', 'wp-slimstat'), 'callback' => [self::class, 'raw_results_to_html'], 'callback_args' => [ - 'type' => 'recent', - 'columns' => 'searchterms', - 'where' => 'content_type LIKE "%%search%%" AND searchterms <> "" AND searchterms IS NOT NULL', - 'raw' => ['wp_slimstat_db', 'get_recent'], + 'type' => 'recent', + 'columns' => 'searchterms', + 'where' => 'content_type LIKE %s AND searchterms <> "" AND searchterms IS NOT NULL', + 'where_params' => ['%search%'], + 'raw' => ['wp_slimstat_db', 'get_recent'], ], 'classes' => ['normal'], 'locations' => ['slimview4'], @@ -700,7 +704,8 @@ 'callback_args' => [ 'type' => 'top', 'columns' => 'searchterms', - 'where' => 'content_type LIKE "%%search%%" AND searchterms <> "" AND searchterms IS NOT NULL', + 'where' => 'content_type LIKE %s AND searchterms <> "" AND searchterms IS NOT NULL', + 'where_params' => ['%search%'], 'raw' => ['wp_slimstat_db', 'get_top'], ], 'classes' => ['normal'], @@ -926,6 +931,9 @@ } } + // Fire action hook for third-party integrations + do_action('wp_slimstat_reports_init'); + // We store page titles in a transient for improved performance if (empty($_REQUEST['page']) || !in_array($_REQUEST['page'], ['slimlayout', 'slimadddons'])) { self::$resource_titles = get_transient('slimstat_resource_titles'); @@ -944,15 +952,41 @@ return false; } + // Get postbox configuration + $postbox_config = self::$reports[$_report_id]['postbox_config'] ?? []; + $hide_header = $postbox_config['hide_header'] ?? false; + $hide_padding = $postbox_config['hide_padding'] ?? false; + $custom_height = $postbox_config['custom_height'] ?? null; + $full_width = $postbox_config['full_width'] ?? false; + $no_border = $postbox_config['no_border'] ?? false; + $no_background = $postbox_config['no_background'] ?? false; + $header_classes = empty(self::$reports[$_report_id]['classes']) ? '' : implode(' ', self::$reports[$_report_id]['classes']); $fixed_title = str_replace(['-', '_', '"', "'", ')', '('], '', strtolower(self::$reports[$_report_id]['title'])); $header_classes .= ' report-' . implode('-', explode(' ', esc_attr($fixed_title))); + + // Add postbox configuration classes + if ($hide_header) { + $header_classes .= ' slimstat-hide-header'; + } + if ($hide_padding) { + $header_classes .= ' slimstat-hide-padding'; + } + if ($full_width) { + $header_classes .= ' slimstat-full-width'; + } + if ($no_border) { + $header_classes .= ' slimstat-no-border'; + } + if ($no_background) { + $header_classes .= ' slimstat-no-background'; + } $header_buttons = ''; $header_tooltip = ''; $widget_title = ''; // Don't show the header buttons on the frontend - if (is_admin()) { + if (is_admin() && !$hide_header) { // Show the refresh button only if the time range is not in the past if (wp_slimstat_db::$filters_normalized['utime']['end'] >= date_i18n('U') - 300) { $header_buttons = '<a class="noslimstat refresh" title="' . __('Refresh', 'wp-slimstat') . '" href="' . self::fs_url() . '"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M2.44215 9.33359C2.50187 5.19973 5.89666 1.875 10.0656 1.875C12.8226 1.875 15.239 3.32856 16.5777 5.50601C16.7584 5.80006 16.6666 6.18499 16.3726 6.36576C16.0785 6.54654 15.6936 6.45471 15.5128 6.16066C14.3937 4.34037 12.3735 3.125 10.0656 3.125C6.57859 3.125 3.75293 5.89808 3.69234 9.33181L4.02599 9.00077C4.27102 8.75765 4.66675 8.75921 4.90986 9.00424C5.15298 9.24928 5.15143 9.645 4.90639 9.88812L3.50655 11.277C3.26288 11.5188 2.86982 11.5188 2.62614 11.277L1.2263 9.88812C0.981267 9.645 0.979713 9.24928 1.22283 9.00424C1.46595 8.75921 1.86167 8.75765 2.10671 9.00077L2.44215 9.33359ZM16.4885 8.72215C16.732 8.4815 17.1238 8.4815 17.3672 8.72215L18.7724 10.111C19.0179 10.3537 19.0202 10.7494 18.7776 10.9949C18.5349 11.2404 18.1392 11.2427 17.8937 11.0001L17.5521 10.6624C17.4943 14.8003 14.0846 18.125 9.90191 18.125C7.13633 18.125 4.71134 16.6725 3.3675 14.4949C3.18622 14.2012 3.2774 13.8161 3.57114 13.6348C3.86489 13.4535 4.24997 13.5447 4.43125 13.8384C5.5545 15.6586 7.58316 16.875 9.90191 16.875C13.4071 16.875 16.2433 14.0976 16.302 10.6641L15.962 11.0001C15.7165 11.2427 15.3208 11.2404 15.0782 10.9949C14.8355 10.7494 14.8378 10.3537 15.0833 10.111L16.4885 8.72215Z" fill="#676E74"/></svg></a>'; @@ -971,7 +1005,13 @@ $bar_color = (empty(self::$reports[$_report_id]['color'])) ? '#EFF6FF' : self::$reports[$_report_id]['color']; - echo "<div class='postbox " . esc_attr($header_classes) . "' style='--box-bar-color: " . esc_attr($bar_color) . ";' id='" . esc_attr($_report_id) . sprintf("'>%s %s <div class='inside'>", $header_buttons, $widget_title); + // Build style attributes + $style_attrs = "--box-bar-color: " . esc_attr($bar_color); + if ($custom_height) { + $style_attrs .= "; height: " . esc_attr($custom_height); + } + + echo "<div class='postbox " . esc_attr($header_classes) . "' style='" . esc_attr($style_attrs) . "' id='" . esc_attr($_report_id) . sprintf("'>%s %s <div class='inside'>", $header_buttons, $widget_title); return null; } @@ -1046,9 +1086,22 @@ } wp_slimstat_db::$debug_message = ''; + $where_params = $_args['where_params'] ?? null; + if (!empty($_args['raw']) && is_array($_args['raw']) && isset($_args['raw'][0]) && method_exists($_args['raw'][0], 'get_combined_where')) { + $_args['where'] = call_user_func([$_args['raw'][0], 'get_combined_where'], $_args['where'], '', true, '', $where_params); + } $all_results = call_user_func($_args['raw'], $_args); + // Fix for Recent Outbound Links: wrap strings as arrays with the correct key + if (!empty($_args['columns']) && 'outbound_resource' === $_args['columns'] && !empty($all_results) && is_array($all_results)) { + foreach ($all_results as $k => $v) { + if (!is_array($v)) { + $all_results[$k] = ['outbound_resource' => $v]; + } + } + } + // Backward compatibility if (!is_array($all_results)) { $all_results = []; @@ -1105,6 +1158,10 @@ $_args['columns'] = explode(',', $_args['columns']); $_args['columns'] = trim($_args['columns'][0]); } + // Ensure columns is a string (for safety) + if (is_array($_args['columns'])) { + $_args['columns'] = trim($_args['columns'][0]); + } $permalinks_enabled = get_option('permalink_structure'); @@ -1112,7 +1169,12 @@ $row_details = ''; $percentage = ''; $element_pre_value = ''; - $element_value = $results[$i][$_args['columns']]; + // Ensure $results[$i] is an array and the key exists + if (is_array($results[$i]) && isset($results[$i][$_args['columns']])) { + $element_value = $results[$i][$_args['columns']]; + } else { + $element_value = ''; + } // Some columns require a special pre-treatment switch ($_args['columns']) { @@ -1121,14 +1183,14 @@ $element_pre_value = self::inline_help($results[$i]['user_agent'], false); } - if (realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/browsers/' . strtolower($results[$i]['browser']) . '.png'))) { - $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/browsers/' . strtolower($results[$i]['browser']) . '.png'); + $browser = $results[$i]['browser'] ?? ''; + if (realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/browsers/' . strtolower($browser) . '.png'))) { + $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/browsers/' . strtolower($browser) . '.png'); } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); } - - $element_value = '<img class="slimstat-browser-icon" src="' . $image_url . '" width="16" height="16" alt="' . $results[$i]['browser'] . '" /> '; - $element_value .= $results[$i]['browser'] . ((isset($results[$i]['browser_version']) && 0 != intval($results[$i]['browser_version'])) ? ' ' . $results[$i]['browser_version'] : ''); + $element_value = '<img class="slimstat-browser-icon" src="' . $image_url . '" width="16" height="16" alt="' . $browser . '" /> '; + $element_value .= $browser . ((isset($results[$i]['browser_version']) && 0 != intval($results[$i]['browser_version'])) ? ' ' . $results[$i]['browser_version'] : ''); break; case 'category': @@ -1160,18 +1222,18 @@ break; case 'country': - - if (realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/flags/' . strtolower($results[$i]['country']) . '.svg'))) { - $svg_path = realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/flags/' . strtolower($results[$i]['country']) . '.svg')); - $svg_content = file_get_contents($svg_path); - $element_value = '<span class="slimstat-flag-container">' . $svg_content . '</span>'; + $country = $results[$i]['country'] ?? ''; + $flag_rel = '/admin/assets/images/flags/' . strtolower($country) . '.svg'; + $flag_path = SLIMSTAT_ANALYTICS_DIR . $flag_rel; + if (is_readable($flag_path)) { + $image_url = SLIMSTAT_ANALYTICS_URL . $flag_rel; + $element_value = '<img class="slimstat-flag-icon" src="' . $image_url . '" width="16" height="16" alt="' . esc_attr($country) . '" />'; } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); - $element_value = '<img class="slimstat-browser-icon" src="' . $image_url . '" width="16" height="16" alt="' . $results[$i]['country'] . '" />'; + $element_value = '<img class="slimstat-flag-icon" src="' . $image_url . '" width="16" height="16" alt="' . esc_attr($country) . '" />'; } - - $row_details .= __('Code', 'wp-slimstat') . (': ' . $results[ $i ][ 'country' ]); - $element_value .= wp_slimstat_i18n::get_string('c-' . $results[$i]['country']); + $row_details .= __('Code', 'wp-slimstat') . (': ' . $country); + $element_value .= wp_slimstat_i18n::get_string('c-' . $country); break; case 'id': @@ -1187,9 +1249,12 @@ $language_parts = explode('-', $results[$i][$_args['columns']]); $last_language_part = end($language_parts); if (realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/flags/' . $last_language_part . '.svg'))) { - $svg_path = realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/flags/' . $last_language_part . '.svg')); - $svg_content = file_get_contents($svg_path); - $element_value = '<span class="slimstat-flag-container">' . $svg_content . '</span>'; + $flag_rel = '/admin/assets/images/flags/' . $last_language_part . '.svg'; + $flag_path = SLIMSTAT_ANALYTICS_DIR . $flag_rel; + if (is_readable($flag_path)) { + $image_url = SLIMSTAT_ANALYTICS_URL . $flag_rel; + $element_value = '<img class="slimstat-flag-icon" src="' . $image_url . '" width="16" height="16" alt="' . esc_attr($last_language_part) . '" />'; + } } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); $element_value = '<img class="slimstat-browser-icon" src="' . $image_url . '" width="16" height="16" alt="' . $results[$i][$_args['columns']] . '" />'; @@ -1200,7 +1265,6 @@ break; case 'platform': - $row_details = __('Code', 'wp-slimstat') . (': ' . $results[$i][$_args[ 'columns' ]]); $icons = [ 'android' => 'and', @@ -1217,8 +1281,8 @@ 'macosx' => 'mac', ]; - $platform_parts = explode('-', $results[$i][$_args['columns']]); - $last_platform_part = strtolower(end($platform_parts)); + $platform_parts = explode('-', $results[$i][$_args['columns']] ?? ''); + $last_platform_part = strtolower((string)end($platform_parts)); if (realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/os/' . $last_platform_part . '.webp'))) { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/os/' . $last_platform_part . '.webp'); @@ -1247,9 +1311,9 @@ $row_details = __('URL', 'wp-slimstat') . ': ' . htmlentities($results[$i][$_args['columns']], ENT_QUOTES, 'UTF-8'); } if (!empty($_args['where']) && false !== strpos($_args['where'], 'download')) { - $clean_extension = pathinfo(strtolower(parse_url($results[$i][$_args['columns']], PHP_URL_PATH)), PATHINFO_EXTENSION); + $clean_extension = pathinfo(strtolower(parse_url($results[$i][$_args['columns']] ?? '', PHP_URL_PATH)), PATHINFO_EXTENSION); if (in_array($clean_extension, ['jpg', 'gif', 'png', 'jpeg', 'bmp'])) { - $row_details = '<br><img src="' . $results[$i][$_args['columns']] . '" style="width:100px">'; + $row_details = '<br><img src="' . esc_url($results[$i][$_args['columns']]) . '" style="width:100px">'; } } $element_value = $resource_title; @@ -1278,39 +1342,39 @@ if (!empty($results[$i]['username'])) { $element_custom_value = get_user_by('login', $results[$i]['username']); if ($element_custom_value) { - $element_value = "<a href='" . get_author_posts_url($element_custom_value->ID) . "' class=\"slimstat-author-link\" title='" . esc_attr($element_custom_value->user_login) . "'>"; + $element_value = "<a href='" . esc_url(get_author_posts_url($element_custom_value->ID)) . "' class=\"slimstat-author-link\" title='" . esc_attr($element_custom_value->user_login) . "'>"; $element_value .= get_avatar($element_custom_value->ID, 18); - $element_value .= $results[$i]['username']; + $element_value .= esc_html($results[$i]['username']); $element_value .= '</a>'; } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); - $element_value = "<a href=\"#\" class='slimstat-author-link'><img src='" . $image_url . sprintf("' class=\"avatar avatar-16 photo\" alt='Unknown'>%s (", $results[$i]['username']) . __('Unknown', 'wp-slimstat') . ')</a>'; + $element_value = "<a href=\"#\" class='slimstat-author-link'><img src='" . esc_url($image_url) . sprintf("' class=\"avatar avatar-16 photo\" alt='Unknown'>%s (", esc_html($results[$i]['username'])) . __('Unknown', 'wp-slimstat') . ')</a>'; } } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); - $element_value = "<a href=\"#\" class='slimstat-author-link'><img src='" . $image_url . "' class=\"avatar avatar-16 photo\" alt='Unknown'>" . __('Guest', 'wp-slimstat') . '</a>'; + $element_value = "<a href=\"#\" class='slimstat-author-link'><img src='" . esc_url($image_url) . "' class=\"avatar avatar-16 photo\" alt='Unknown'>" . __('Guest', 'wp-slimstat') . '</a>'; } if ('on' == wp_slimstat::$settings['show_display_name']) { $element_custom_value = get_user_by('login', $results[$i]['username']); if (is_object($element_custom_value)) { - $element_value = $element_custom_value->display_name; + $element_value = esc_html($element_custom_value->display_name); } } break; case 'author': // Backward compatibility - $author_username = $results[$i]['author']; + $author_username = is_array($results[$i]) && isset($results[$i]['author']) ? $results[$i]['author'] : ''; if ($author_username) { $author = get_user_by('login', $author_username); if ($author) { - $author_id = $author->ID; - $element_value = "<a href='" . get_author_posts_url($author_id) . "' class=\"slimstat-author-link\" title='" . esc_attr($author->user_login) . "'>"; + $author_id = $author ? $author->ID : 0; + $element_value = "<a href='" . esc_url(get_author_posts_url($author_id)) . "' class=\"slimstat-author-link\" title='" . esc_attr($author->user_login) . "'>"; $element_value .= get_avatar($author_id, 18); - $element_value .= $author ? empty($author->display_name) ? $author->user_login : $author->display_name : $results[$i]['author']; + $element_value .= esc_html($author ? (empty($author->display_name) ? $author->user_login : $author->display_name) : $author_username); $element_value .= '</a>'; } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); - $element_value = "<a href=\"#\" class='slimstat-author-link'><img src='" . $image_url . sprintf("' class=\"avatar avatar-16 photo\" alt='Unknown'>%s (", $results[$i]['author']) . __('Unknown', 'wp-slimstat') . ')</a>'; + $element_value = "<a href=\"#\" class='slimstat-author-link'><img src='" . esc_url($image_url) . "' class=\"avatar avatar-16 photo\" alt='Unknown'>" . esc_html($author_username) . ' (' . __('Unknown', 'wp-slimstat') . ')</a>'; } } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); @@ -1328,11 +1392,16 @@ } if (is_admin()) { - $element_value = "<a class='slimstat-filter-link' href='" . self::fs_url($_args['columns'] . ' ' . $_args['filter_op'] . ' ' . htmlentities(strval($results[$i][$_args['columns']]), ENT_QUOTES, 'UTF-8')) . sprintf("'>%s</a>", $element_value); + $column_value = is_array($results[$i]) && isset($results[$i][$_args['columns']]) ? $results[$i][$_args['columns']] : ''; + $element_value = "<a class='slimstat-filter-link' href='" . self::fs_url($_args['columns'] . ' ' . $_args['filter_op'] . ' ' . htmlentities(strval($column_value), ENT_QUOTES, 'UTF-8')) . sprintf("'>%s</a>", $element_value); } if (!empty($_args['type']) && 'recent' == $_args['type']) { - $row_details = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $results[$i]['dt'], true) . ('' === $row_details || '0' === $row_details ? '' : '<br>') . $row_details; + if (is_array($results[$i]) && isset($results[$i]['dt'])) { + $row_details = date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $results[$i]['dt'], true) . ('' === $row_details || '0' === $row_details ? '' : '<br>') . $row_details; + } else { + // No date available, just show details if any + } } if (!empty($_args['type']) && 'top' == $_args['type']) { @@ -1356,19 +1425,19 @@ $base_url = parse_url(get_site_url($results[$i]['blog_id'])); $base_url = $base_url['scheme'] . '://' . $base_url['host']; } - $element_value = '<a target="_blank" class="slimstat-font-logout" title="' . __('Open this URL in a new window', 'wp-slimstat') . '" href="' . $base_url . htmlentities($results[$i]['resource'], ENT_QUOTES, 'UTF-8') . '"></a> ' . $base_url . $element_value; + $element_value = '<a target="_blank" class="slimstat-font-logout" title="' . esc_attr(__('Open this URL in a new window', 'wp-slimstat')) . '" href="' . esc_url($base_url . $results[$i]['resource']) . '"></a> ' . esc_html($base_url) . $element_value; } if ('referer' == $_args['columns'] && !empty($_args['type']) && 'top' == $_args['type']) { - $element_url = htmlentities($results[$i]['referer'], ENT_QUOTES, 'UTF-8'); + $element_url = $results[$i]['referer']; if (false === strpos($element_url, 'http')) { $element_url = 'https://' . $element_url; } - $element_value = '<a target="_blank" class="slimstat-font-logout" title="' . __('Open this URL in a new window', 'wp-slimstat') . '" href="' . $element_url . '"></a> ' . $element_value; + $element_value = '<a target="_blank" class="slimstat-font-logout" title="' . esc_attr(__('Open this URL in a new window', 'wp-slimstat')) . '" href="' . esc_url($element_url) . '"></a> ' . $element_value; } if (is_admin() && !empty($results[$i]['ip']) && 'ip' != $_args['columns'] && 'on' != wp_slimstat::$settings['convert_ip_addresses']) { - $row_details .= '<br> IP: <a class="slimstat-filter-link" href="' . self::fs_url('ip equals ' . $results[$i]['ip']) . '">' . $results[$i]['ip'] . '</a>' . (empty($results[$i]['other_ip']) ? '' : ' / ' . $results[$i]['other_ip']) . '<a title="WHOIS: ' . $results[$i]['ip'] . '" class="slimstat-font-location-1 whois" href="' . wp_slimstat::$settings['ip_lookup_service'] . $results[$i]['ip'] . '"></a>'; + $row_details .= '<br> IP: <a class="slimstat-filter-link" href="' . esc_url(self::fs_url('ip equals ' . $results[$i]['ip'])) . '">' . esc_html($results[$i]['ip']) . '</a>' . (empty($results[$i]['other_ip']) ? '' : ' / ' . esc_html($results[$i]['other_ip'])) . '<a title="WHOIS: ' . esc_attr($results[$i]['ip']) . '" class="slimstat-font-location-1 whois" href="' . esc_url(wp_slimstat::$settings['ip_lookup_service'] . $results[$i]['ip']) . '"></a>'; } if ('' !== $row_details && '0' !== $row_details) { $row_details = sprintf("<b class='slimstat-tooltip-content'>%s</b>", $row_details); @@ -1530,7 +1599,7 @@ } } - echo sprintf('<p>%s <span>%s</span><br/>', $a_result[ 'resource' ], esc_html( $a_result[ 'counthits' ] )) . implode(', ', $group_markup) . '</p>'; + echo sprintf('<p>%s <span>%s</span><br/>', wp_kses_post( $a_result[ 'resource' ] ), esc_html( $a_result[ 'counthits' ] )) . wp_kses_post( implode(', ', $group_markup) ) . '</p>'; } if (! defined('DOING_AJAX') || ! DOING_AJAX) { @@ -1549,6 +1618,7 @@ public static function show_rankings() { + // Remove Alexa ranking code and references $options = ['timeout' => 30, 'headers' => ['Accept' => 'application/json']]; $site_url = parse_url(home_url(), PHP_URL_HOST); if (!empty(wp_slimstat_db::$filters_normalized['resource']) && 'equals' == wp_slimstat_db::$filters_normalized['resource'][0]) { @@ -1556,7 +1626,7 @@ } $site_url = urlencode($site_url); - // Check if we have a valied transient + // Check if we have a valid transient if (false === ($rankings = get_transient('slimstat_ranking_values'))) { $rankings = [ 'seomoz_domain_authority' => [ @@ -1574,21 +1644,6 @@ __('Moz Links', 'wp-slimstat'), __('The number of links (external, equity or nonequity or not) to your homepage.', 'wp-slimstat'), ], - 'alexa_world_rank' => [ - 0, - __('Alexa World Rank', 'wp-slimstat'), - __('Alexa is a subsidiary company of Amazon.com which provides commercial web traffic data.', 'wp-slimstat'), - ], - 'alexa_country_rank' => [ - 0, - __('Alexa Country Rank', 'wp-slimstat'), - '', - ], - 'alexa_popularity' => [ - 0, - __('Alexa Popularity', 'wp-slimstat'), - '', - ], ]; if (!empty(wp_slimstat::$settings['mozcom_access_id']) && !empty(wp_slimstat::$settings['mozcom_secret_key'])) { @@ -1616,40 +1671,6 @@ } } } - - // Alexa - $response = @wp_remote_get('http://data.alexa.com/data?cli=10&dat=snbamz&url=' . $site_url, $options); - if (!is_wp_error($response) && isset($response['response']['code']) && (200 == $response['response']['code']) && !empty($response['body'])) { - $response = @simplexml_load_string($response['body']); - if (is_object($response->SD[1])) { - if ($response->SD[1]->POPULARITY && $response->SD[1]->POPULARITY->attributes()) { - $popularity = $response->SD[1]->POPULARITY->attributes(); - if (!empty($popularity)) { - $rankings['alexa_popularity'][0] = number_format_i18n(floatval($popularity['TEXT'])); - } - } - - if ($response->SD[1]->REACH && $response->SD[1]->REACH->attributes()) { - $reach = $response->SD[1]->REACH->attributes(); - if (!empty($reach)) { - $rankings['alexa_world_rank'][0] = number_format_i18n(floatval($reach['RANK'])); - } - } - - if ($response->SD[1]->COUNTRY && $response->SD[1]->COUNTRY->attributes()) { - $country = $response->SD[1]->COUNTRY->attributes(); - if (!empty($country)) { - $rankings['alexa_country_rank'][0] = number_format_i18n(floatval($country['RANK'])); - } - } elseif ($response->SD[1]->RANK && $response->SD[1]->RANK->attributes()) { - $rank = $response->SD[1]->RANK->attributes(); - if (!empty($rank)) { - $rankings['alexa_country_rank'][0] = number_format_i18n(floatval($rank['DELTA'])); - $rankings['alexa_country_rank'][1] = __('Alexa Delta', 'wp-slimstat'); - } - } - } - } } foreach ($rankings as $a_ranking) { @@ -1684,10 +1705,10 @@ $max = 0; foreach ($countries as $a_country) { - $code = strtolower($a_country['country']); + $code = strtolower((string)($a_country['country'] ?? '')); $visits = (int) $a_country['counthits']; $percent = (wp_slimstat_db::$pageviews > 0) ? round((100 * $visits / wp_slimstat_db::$pageviews), 2) : 0; - $country_name = wp_slimstat_i18n::get_string('c-' . $a_country['country'], 'wp-slimstat'); + $country_name = wp_slimstat_i18n::get_string('c-' . ($a_country['country'] ?? ''), 'wp-slimstat'); $data_areas[$code] = $visits; $country_stats[] = [ @@ -1706,8 +1727,8 @@ $top_countries = array_slice($country_stats, 0, 5); $path_slimstat = dirname(__FILE__, 2); - wp_enqueue_script('slimstat_jqvmap', plugins_url('/admin/assets/js/jqvmap/jquery.vmap.min.js', $path_slimstat), ['jquery'], '1.5.1', false); - wp_enqueue_script('slimstat_jqvmap_world', plugins_url('/admin/assets/js/jqvmap/jquery.vmap.world.min.js', $path_slimstat), ['jquery'], '1.5.1', false); + wp_enqueue_script('slimstat_jqvmap', plugins_url('/admin/assets/js/jqvmap/jquery.vmap.min.js', $path_slimstat), ['jquery'], '1.5.1', true); + wp_enqueue_script('slimstat_jqvmap_world', plugins_url('/admin/assets/js/jqvmap/jquery.vmap.world.min.js', $path_slimstat), ['jquery'], '1.5.1', true); ?> <div class="map-container"> @@ -1724,17 +1745,29 @@ } else { $settings_url = network_admin_url('admin.php?page=slimconfig&tab='); } - if (('disable' == wp_slimstat::$settings['enable_maxmind'] || !\SlimStat\Services\GeoIP::database_exists())) { - echo sprintf(__("GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor.", 'wp-slimstat'), $settings_url . '2#wp-slimstat-third-party-libraries'); - echo '<br>'; - } - ?> + // Provider-aware GeoIP notice (world map): only for DB providers when DB file is missing + $provider = wp_slimstat::$settings['geolocation_provider'] ?? 'dbip'; + $uses_db = in_array($provider, ['dbip', 'maxmind'], true); + $db_missing = false; + if ($uses_db) { + try { + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + $db_missing = !file_exists($service->getProvider()->getDbPath()); + } catch (\Throwable $e) { + $db_missing = true; + } + } + if ($uses_db && $db_missing) { + echo sprintf(__("GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor.", 'wp-slimstat'), $settings_url . '2#wp-slimstat-third-party-libraries'); + echo '<br>'; + } + ?> <?php foreach ($top_countries as $country): ?> <div class="country-bar"> <div class="country-flag-container"> <?php - if (realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/flags/' . strtolower($country['code']) . '.svg'))) { - $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/flags/' . strtolower($country['code']) . '.svg'); + if (realpath(SLIMSTAT_ANALYTICS_DIR . ('/admin/assets/images/flags/' . strtolower((string)($country['code'] ?? '')) . '.svg'))) { + $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/flags/' . strtolower((string)($country['code'] ?? '')) . '.svg'); echo '<img class="country-flag" src="' . $image_url . '" width="32" height="32" alt="' . $country['code'] . '" />'; } else { $image_url = SLIMSTAT_ANALYTICS_URL . ('/admin/assets/images/unk.png'); @@ -1761,10 +1794,10 @@ hoverOpacity: 0.7, showTooltip: true, normalizeFunction: 'polynomial', - values: <?php echo json_encode($data_areas); ?>, + values: <?php echo wp_json_encode($data_areas); ?>, enableZoom: true, onLabelShow: function (event, label, code) { - const data = <?php echo json_encode($country_stats); ?>; + const data = <?php echo wp_json_encode($country_stats); ?>; const country = data.find(c => c.code === code); if (country) { label.html( '<canvas></canvas><h3>' + country.name + '</h3><p>' + country.visits.toLocaleString() + ' Visitors</p>'); @@ -1855,7 +1888,7 @@ // Avoid XSS attacks ( why would the owner try to hack into his/her own website though? ) if (!empty($_SERVER['HTTP_REFERER'])) { - $parsed_referer = parse_url(sanitize_url(wp_unslash($_SERVER['HTTP_REFERER']))); + $parsed_referer = parse_url(sanitize_url(wp_unslash($_SERVER['HTTP_REFERER'])) ?: ''); if (!$parsed_referer || (isset($parsed_referer['scheme']) && ('' !== $parsed_referer['scheme'] && '0' !== $parsed_referer['scheme']) && !in_array(strtolower($parsed_referer['scheme']), ['http', 'https']))) { return ''; } @@ -1894,13 +1927,20 @@ */ public static function get_resource_title($_resource = '') { + // Ensure $_resource is never null to avoid PHP 8.1+ deprecation warnings + $_resource = $_resource ?? ''; + if ('on' != wp_slimstat::$settings['convert_resource_urls_to_titles']) { return htmlentities(urldecode($_resource), ENT_QUOTES, 'UTF-8'); } // Do we already have this value in our transient cache? - $cache_index = md5($_resource); - if (!empty(self::$resource_titles) && !empty(self::$resource_titles[$cache_index])) { + $cache_index = md5((string)$_resource); + if (!isset(self::$resource_titles) || !is_array(self::$resource_titles)) { + $transient = get_transient('slimstat_resource_titles'); + self::$resource_titles = is_array($transient) ? $transient : []; + } + if (!empty(self::$resource_titles[$cache_index])) { return self::$resource_titles[$cache_index]; } @@ -1914,7 +1954,7 @@ // Encode URLs to avoid XSS attacks if (self::$resource_titles[$cache_index] == $_resource) { - self::$resource_titles[$cache_index] = htmlspecialchars(self::$resource_titles[$cache_index], ENT_QUOTES, 'UTF-8'); + self::$resource_titles[$cache_index] = esc_html(self::$resource_titles[$cache_index]); } } // Is this a category or tag permalink? else { @@ -1946,13 +1986,12 @@ if ([] !== $term_names) { self::$resource_titles[$cache_index] = esc_html( implode(',', $term_names) ); } else { - self::$resource_titles[$cache_index] = htmlspecialchars(self::$resource_titles[$cache_index], ENT_QUOTES, 'UTF-8'); + self::$resource_titles[$cache_index] = esc_html(self::$resource_titles[$cache_index]); } } - // Save new value in cache + // Save new value in cache only if changed set_transient('slimstat_resource_titles', self::$resource_titles, 1800); - return self::$resource_titles[$cache_index]; } @@ -1987,7 +2026,7 @@ if (!empty($_POST['report_id'])) { check_ajax_referer('meta-box-order', 'security'); // Let's make sure the request is coming from an authorized source - $report_id = $_POST['report_id']; + $report_id = sanitize_key(wp_unslash($_POST['report_id'])); } elseif (!empty($_args['id'])) { $report_id = $_args['id']; } Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0: assets @@ -1,3 +1,60 @@ += 5.4.0 - 2026-03-08 = + +- Full release notes → [WordPress Real-time Analytics Plugin](https://wp-slimstat.com/wordpress-analytics-plugin-slimstat-5-4-release-notes/?utm_source=wordpress&utm_medium=changelog&utm_campaign=changelog&utm_content=5-4-0) – Slimstat 5.4 – Real-Time, Real Privacy + +Breaking +- Legacy internal REST/tracker APIs changed; custom add-ons using old internals must update. See the Migration Guide for details. + +New +- View real-time site stats directly from the WordPress admin bar — see online visitors, pageviews, and top pages at a glance. +- Hover over any bar in the real-time chart to see detailed analytics in an interactive tooltip. +- Refreshed analytics experience with a redesigned header and richer real-time visuals. +- Integration with Consent Management Platforms (CMPs) for GDPR compliance: WP Consent API support with configurable consent categories +- GDPR Compliance Mode toggle - Enable/disable GDPR compliance requirements (default: enabled) +- Consent change listener that automatically resumes tracking when user grants consent via CMP. +- Centralized consent utility (`Consent` class) for tracking eligibility and PII operations. +- GDPR-compliant salted hash IP address functionality with daily salt rotation for enhanced privacy protection. +- IP-based rate limiting for AJAX tracking to prevent excessive requests and improve security. +- WordPress Privacy Policy content registration for GDPR Article 13/14 compliance with built-in data export/erase support. +- Admin migration tools to optimize key database indexes and improve report responsiveness. + +Enhancement +- Optimized script enqueuing to load scripts in the footer for better page load times. +- Redesigned date picker with persistent date range for improved reporting UX. +- Improved flag icon rendering in reports with better readability checks. +- Optimized transient caching for remote API responses in the i18n module. +- Refactored code structure with new Query manager and improved caching system. +- Added 'Clear Cache' button functionality for better cache management. +- Moved Query class to src directory following PSR-4 autoloading standards. +- The geolocation functionality has been completely refactored and improved. It now correctly handles different providers, including DB-IP (the default), MaxMind (which requires a free license key), and Cloudflare. The underlying logic for downloading, updating, and using the geolocation database has been fixed to ensure that location data is accurately tracked and stored. +- Refactored GDPR architecture - consent management fully delegated to external CMPs. +- Smart IP handling - automatically upgrades from anonymized/hashed IP to full IP when consent is granted. +- Improved JavaScript consent handling with polling-based consent state monitoring. +- Enhanced code quality with proper namespace imports and Query builder pattern throughout. +- Conditional fingerprint storage - only collected when PII is allowed. +- Better privacy controls - anonymous tracking option prevents all PII collection. +- Code modernization with arrow functions and improved caching in Query class. +- Default data retention period set to 420 days (14 months) for GDPR compliance. + +Fix +- Fixed SlimStat JavaScript API not being accessible to external code (e.g., opt-out buttons, custom tracking) after bundling changes ([#121](https://github.com/wp-slimstat/wp-slimstat/issues/121), [#109](https://github.com/wp-slimstat/wp-slimstat/issues/109)) +- Fixed FingerprintJS v4 compatibility issues that could affect visitor fingerprint tracking. +- Fixed date-range, timezone, and report-filter issues that caused incorrect analytics output. +- Fixed IP processing and geolocation edge cases for more accurate visitor reporting. +- Enhanced query security and cleanup for better data integrity. +- Optimized database indexing, caching, and AJAX handling. +- Legacy mode now conservatively denies PII collection when GDPR enabled and no CMP configured. +- Consent revocation properly deletes tracking cookie when user opts out via banner. +- Removed legacy cookie-based opt-in/opt-out handling for cleaner, CMP-based consent flow. + +Security +- Stronger SQL/XSS protections, stricter nonce validation, timing-safe HMAC checks, and improved IP hashing/anonymization. + +> **For add-on developers**: This release includes breaking API changes. Add-ons using `RESTService`, direct property access to `$data_js`/`$stat`, or calling `wp_slimstat::slimtrack()` directly must be updated. + += 5.3.6 = +* Security: Hardened output escaping in reports + = 5.3.5 - 2025-12-31 = * Security: Hardened plugin security @@ -18,8 +75,13 @@ - **Enhancement**: Enhanced responsive design for the "Access Log" report. - **Enhancement**: Improved tracking logic to prevent duplicate pageviews and events. - **Enhancement**: Enhanced interaction tracking and heartbeat finalization. +- **New**: Real-time data visualization with live analytics dashboard for instant insights. +- **New**: GDPR-compliant salted hash IP address functionality for enhanced privacy protection. +- **New**: IP-based rate limiting for AJAX tracking to prevent excessive requests and improve security. = 5.3.0 - 2025-08-25 = +- Full release notes → [WordPress Real-time Analytics Plugin](https://wp-slimstat.com/wordpress-analytics-plugin-slimstat-5-3-release-notes/?utm_source=wordpress&utm_medium=changelog&utm_campaign=changelog&utm_content=5-3-0) – Slimstat 5.3 – Smarter Charts, Better Tracking + - **New**: Tracker type options (REST API + Ad-blocker bypass) for improved tracking flexibility. - **New**: Support for WordPress date format setting in charts. - **New**: Hourly, daily, weekly, monthly, and yearly chart granularities for deeper insights. Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0: .coderabbit.yaml Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/languages: i18n-v3.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/languages: i18n-wordpressorg-v3.php @@ -1,21 +1,22 @@ -# Copyright (C) 2025 Jason Crouse, VeronaLabs +# Copyright (C) 2026 Jason Crouse, VeronaLabs # This file is distributed under the GPL-2.0+. msgid "" msgstr "" -"Project-Id-Version: SlimStat Analytics 5.3.5\n" +"Project-Id-Version: SlimStat Analytics 5.4.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-slimstat\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2025-12-31T08:38:08+00:00\n" +"POT-Creation-Date: 2026-03-08T10:42:32+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.12.0\n" "X-Domain: wp-slimstat\n" #. Plugin Name of the plugin #: wp-slimstat.php +#: wp-slimstat.php:1233 msgid "SlimStat Analytics" msgstr "" @@ -36,7 +37,7 @@ msgstr "" #: admin/config/index.php:21 -#: admin/config/index.php:106 +#: admin/config/index.php:95 msgid "Tracker" msgstr "" @@ -129,16 +130,16 @@ msgstr "" #: admin/config/index.php:81 -#: admin/view/wp-slimstat-db.php:107 -#: admin/view/wp-slimstat-db.php:1115 -#: admin/view/wp-slimstat-db.php:1121 -#: admin/view/wp-slimstat-db.php:1127 -#: admin/view/wp-slimstat-db.php:1133 -#: admin/view/wp-slimstat-db.php:1139 -#: admin/view/wp-slimstat-db.php:1145 -#: admin/view/wp-slimstat-db.php:1151 -#: admin/view/wp-slimstat-reports.php:1345 -#: admin/view/wp-slimstat-reports.php:1348 +#: admin/view/wp-slimstat-db.php:110 +#: admin/view/wp-slimstat-db.php:1366 +#: admin/view/wp-slimstat-db.php:1372 +#: admin/view/wp-slimstat-db.php:1378 +#: admin/view/wp-slimstat-db.php:1384 +#: admin/view/wp-slimstat-db.php:1390 +#: admin/view/wp-slimstat-db.php:1396 +#: admin/view/wp-slimstat-db.php:1402 +#: admin/view/wp-slimstat-reports.php:1414 +#: admin/view/wp-slimstat-reports.php:1417 msgid "Hits" msgstr "" @@ -150,1323 +151,1604 @@ msgid "Customize the information displayed when activating the option here above: <strong>hits</strong> refers to the total amount of pageviews, regardless of the user; <strong>(unique) IPs</strong> displays the amount of distinct IP addresses tracked in the given time range." msgstr "" -#: admin/config/index.php:88 -msgid "Database" +#: admin/config/index.php:87 +msgid "Slimstat Notifications" msgstr "" -#: admin/config/index.php:92 -msgid "Data Retention" +#: admin/config/index.php:89 +msgid "Display important notifications inside the plugin, such as new version releases, feature updates, news, and special offers." msgstr "" -#: admin/config/index.php:94 -#: admin/config/index.php:258 -#: admin/view/wp-slimstat-db.php:98 -msgid "days" +#: admin/config/index.php:99 +#: wp-slimstat.php:1273 +msgid "Consent Management" msgstr "" -#: admin/config/index.php:95 -msgid "Enable a daily cron job to erase or archive (see option here below) pageviews older than the number of days specified here. You can enter <strong>0</strong> (the number zero) if you want to disable this feature." +#: admin/config/index.php:103 +msgid "GDPR Compliance Mode" msgstr "" -#: admin/config/index.php:98 -msgid "Archive Records" +#: admin/config/index.php:105 +msgid "<strong>GDPR Compliance:</strong> When enabled, SlimStat requires user consent before tracking (except in Anonymous Tracking mode). When disabled, tracking operates normally without consent checks.<br/><br/><strong>Enabled:</strong> (Recommended for EU/EEA) Tracking requires consent unless Anonymous Tracking mode is active. This ensures GDPR compliance.<br/><strong>Disabled:</strong> Normal tracking without consent checks. Use this only if you are not subject to GDPR regulations (e.g., non-EU websites with no EU visitors)." msgstr "" -#: admin/config/index.php:100 -msgid "If server space is not an issue for you, use this option to archive pageviews to a separate table, instead of deleting them. This will increase performance by reducing the amount of data to process in the main table, while allowing you to access your data at a later time, if needed. Please note that the archive table (<strong>wp_slim_stats_archive</strong>) will be <strong>DELETED</strong> along with all the other tables, when you uninstall Slimstat. Make sure to backup your data before you proceed." +#: admin/config/index.php:108 +msgid "Consent Plugin Integration" msgstr "" #: admin/config/index.php:110 -msgid "Data Protection" +msgid "<strong>GDPR Compliance:</strong> Integrate with a Consent Management Platform (CMP) to ensure tracking only occurs with user consent.<br/><br/><strong>SlimStat Consent Banner:</strong> (Recommended) Use SlimStat's built-in banner with customizable messaging and server-side consent tracking.<br/><strong>Via WP Consent API:</strong> Integrates with CMPs supporting WordPress Consent API (Complianz, CookieYes, etc.). Server-side consent checking available for both modes." msgstr "" -#: admin/config/index.php:114 -msgid "Privacy Mode" +#: admin/config/index.php:112 +msgid "SlimStat Consent Banner (Recommended)" msgstr "" -#: admin/config/index.php:116 -msgid "Mask your visitors' IP addresses (by converting the last number into a zero) and do not track their browser fingerprint, to comply with European privacy laws." +#: admin/config/index.php:113 +msgid "Via WP Consent API" msgstr "" -#: admin/config/index.php:119 -msgid "Set Cookie" +#: admin/config/index.php:122 +msgid "SlimStat Consent Banner" msgstr "" -#: admin/config/index.php:121 -msgid "Disable this option if, for legal or security reasons, you do not want Slimstat to assign a <a href=\"https://en.wikipedia.org/wiki/HTTP_cookie\" target=\"_blank\">cookie</a> to your visitors. Please note that by deactivating this feature, Slimstat will not be able to identify returning visitors as such." +#: admin/config/index.php:131 +msgid "Opt-out Cookies" msgstr "" -#: admin/config/index.php:124 -msgid "Allow Opt-out" +#: admin/config/index.php:133 +msgid "If you are already using another tool to monitor which users opt-out of tracking, and assuming that this tool sets its own cookie to remember their selection, you can enter the cookie names and values in this field to let Slimstat comply with their choice. Please use the following format: <code>cookie_name=value</code>. Slimstat will track any visitors who either don't send a cookie with that name, or send a cookie whose value <strong>does not CONTAIN</strong> the string you specified. If your tool uses structured values like JSON or similar encodings, find the substring related to tracking and enter that as the value here below. For example, <a href='https://wordpress.org/plugins/smart-cookie-kit/' target='_blank'>Smart Cookie Kit</a> uses something like <code>{\"settings\":{\"technical\":true,\"slimstat\":false,\"profiling\":false},\"ver\":\"2.0.0\"}</code>, so your pair should look like: <code>CookiePreferences-your.website.here=\"slimstat\":false</code>. Separate multiple pairs with commas." msgstr "" -#: admin/config/index.php:126 -msgid "The European <a href='https://en.wikipedia.org/wiki/General_Data_Protection_Regulation' target='_blank'>General Data Protection Regulation (GDPR)</a> requires website owners to provide a way for their visitors to opt-out of tracking. By enabling this option, the message here below will be displayed to all users who don't have the corresponding cookie set." +#: admin/config/index.php:141 +msgid "Opt-in Cookies" msgstr "" -#: admin/config/index.php:129 -msgid "Opt-out Cookies" +#: admin/config/index.php:143 +msgid "Similarly to the option here above, you can configure Slimstat to work with an opt-in mechanism. Please use the following format: <code>cookie_name=value</code>. Slimstat will only track visitors who send a cookie whose value <strong>CONTAINS</strong> the string you specified. Separate multiple pairs with commas." msgstr "" -#: admin/config/index.php:131 -msgid "If you are already using another tool to monitor which users opt-out of tracking, and assuming that this tool sets its own cookie to remember their selection, you can enter the cookie names and values in this field to let Slimstat comply with their choice. Please use the following format: <code>cookie_name=value</code>. Slimstat will track any visitors who either don't send a cookie with that name, or send a cookie whose value <strong>does not CONTAIN</strong> the string you specified. If your tool uses structured values like JSON or similar encodings, find the substring related to tracking and enter that as the value here below. For example, <a href='https://wordpress.org/plugins/smart-cookie-kit/' target='_blank'>Smart Cookie Kit</a> uses something like <code>{\"settings\":{\"technical\":true,\"slimstat\":false,\"profiling\":false},\"ver\":\"2.0.0\"}</code>, so your pair should look like: <code>CookiePreferences-your.website.here=\"slimstat\":false</code>. Separate multiple pairs with commas." +#: admin/config/index.php:151 +msgid "Consent Banner Message" msgstr "" -#: admin/config/index.php:134 -msgid "Opt-in Cookies" +#: admin/config/index.php:154 +msgid "Content displayed inside the SlimStat consent banner. Basic HTML (p, a, strong, em) is allowed. Use the editor above to format your message." msgstr "" -#: admin/config/index.php:136 -msgid "Similarly to the option here above, you can configure Slimstat to work with an opt-in mechanism. Please use the following format: <code>cookie_name=value</code>. Slimstat will only track visitors who send a cookie whose value <strong>CONTAINS</strong> the string you specified. Separate multiple pairs with commas." +#: admin/config/index.php:162 +msgid "Accept Button Label" +msgstr "" + +#: admin/config/index.php:166 +msgid "Leave empty to use the default \"Accept\" text." +msgstr "" + +#: admin/config/index.php:174 +msgid "Decline Button Label" +msgstr "" + +#: admin/config/index.php:178 +msgid "Leave empty to use the default \"Deny\" text." +msgstr "" + +#: admin/config/index.php:186 +msgid "Banner Theme Mode" +msgstr "" + +#: admin/config/index.php:188 +msgid "Choose the theme mode for the GDPR consent banner. <strong>Light</strong> uses light colors, <strong>Dark</strong> uses dark colors, and <strong>Auto</strong> follows the user's system preference." msgstr "" -#: admin/config/index.php:139 -msgid "Opt-out Message" +#: admin/config/index.php:190 +msgid "Light Mode" msgstr "" -#: admin/config/index.php:144 -msgid "Customize the message displayed to your visitors here below. Match your website styles and layout by adding the appropriate HTML markup to your message." +#: admin/config/index.php:191 +msgid "Dark Mode" msgstr "" -#: admin/config/index.php:149 +#: admin/config/index.php:192 +msgid "Auto (Follow System)" +msgstr "" + +#: admin/config/index.php:221 +msgid "Data Protection" +msgstr "" + +#: admin/config/index.php:245 +msgid "Anonymize IP Addresses" +msgstr "" + +#: admin/config/index.php:247 +msgid "<strong>GDPR Privacy Protection:</strong> Masks IP addresses before storage (IPv4: 192.168.1.x → 192.168.1.0 / IPv6: last 80 bits removed).<br/><br/>Anonymized IPs cannot identify individual users but still provide useful geographic and network data. <strong>Recommended</strong> for GDPR compliance when not using IP hashing." +msgstr "" + +#: admin/config/index.php:250 +msgid "Hash IP Addresses" +msgstr "" + +#: admin/config/index.php:252 +msgid "<strong>GDPR-Compliant Visitor Counting:</strong> Creates one-way hash from IP + User Agent + daily salt. Hash changes daily, preventing long-term tracking.<br/><br/><strong>Benefits:</strong> Count unique visitors without storing real IPs or using cookies. Original IP cannot be recovered from hash. <strong>Recommended</strong> for GDPR compliance." +msgstr "" + +#: admin/config/index.php:255 +msgid "Set Tracking Cookie" +msgstr "" + +#: admin/config/index.php:257 +msgid "<strong>PII Warning:</strong> Cookies are Personally Identifiable Information under GDPR. Enabling this option requires user consent.<br/><br/><strong>When Disabled:</strong> Cookie-less tracking (more privacy, less accurate return visitor detection)<br/><strong>When Enabled:</strong> Sets a cookie to track returning visitors (better accuracy, requires consent)<br/><br/>Cookies automatically respect consent settings and use Secure, HttpOnly, and SameSite flags for security." +msgstr "" + +#: admin/config/index.php:262 msgid "Link Tracking" msgstr "" -#: admin/config/index.php:153 +#: admin/config/index.php:266 msgid "Same-Domain Referrers" msgstr "" -#: admin/config/index.php:155 +#: admin/config/index.php:268 msgid "By default, when a referrer's domain's pageview is the same as the current site, that information is not saved in the database. However, if you are running a multisite network with subfolders, you might need to enable this option to track same-domain referrers from one site to another, as they are technically 'independent' websites." msgstr "" -#: admin/config/index.php:158 +#: admin/config/index.php:271 msgid "Downloads" msgstr "" -#: admin/config/index.php:160 +#: admin/config/index.php:273 msgid "List all the file extensions that you want to be identified as Downloads. Please note that links pointing to external resources (i.e. PDFs on an external website) will be tracked as Downloads and not Outbound Links, if they match one of the extensions listed here below." msgstr "" -#: admin/config/index.php:165 +#: admin/config/index.php:278 +#: admin/config/index.php:366 msgid "Third-party Libraries" msgstr "" -#: admin/config/index.php:169 -msgid "GeoIP Database Source" +#: admin/config/index.php:282 +msgid "Geolocation Provider" msgstr "" -#: admin/config/index.php:170 -msgid "An error occurred while updating the GeoIP database." +#: admin/config/index.php:285 +msgid "MaxMind GeoLite2 (recommended)" msgstr "" -#: admin/config/index.php:170 -msgid "Update Database" +#: admin/config/index.php:286 +msgid "DB-IP City Lite (free)" msgstr "" -#: admin/config/index.php:170 -msgid "Check Database" +#: admin/config/index.php:287 +msgid "Cloudflare Header" msgstr "" -#: admin/config/index.php:173 -msgid "Disable" +#: admin/config/index.php:289 +msgid "<strong>Choose how Slimstat resolves visitor locations:</strong><br /><strong>DB-IP City Lite</strong> – Free, no license required. Slimstat downloads a local database and updates it automatically in the background after you save settings. You can also run the update manually using the button below. Works for arbitrary IPs in reports.<br /><strong>MaxMind GeoLite2</strong> – Requires a free MaxMind license key. City vs Country precision affects database size and download time. Updates run in the background after saving; you can also update manually. If PHP Phar is disabled on your server, please upload the .mmdb file manually to wp-content/uploads/wp-slimstat/.<br /><strong>Cloudflare Header</strong> – No database needed. Slimstat reads the HTTP_CF_IPCOUNTRY header set by Cloudflare for the current request only. It won't resolve arbitrary test IPs (like 8.8.8.8). Make sure \"IP Geolocation\" is enabled in your Cloudflare dashboard and your site is actually proxied through Cloudflare." msgstr "" -#: admin/config/index.php:174 -msgid "Use the JsDelivr" +#: admin/config/index.php:292 +#: admin/config/index.php:380 +msgid "MaxMind License Key" msgstr "" -#: admin/config/index.php:175 -msgid "Use the MaxMind server with your own license key" +#: admin/config/index.php:294 +msgid "Enter your MaxMind license key to enable automatic downloads of the GeoLite2 database. The license key should be 16-40 characters containing only letters, numbers, and underscores. Required only if you select MaxMind as the provider. <strong>Important:</strong> If the PHP Phar extension is not available on your server, automatic extraction will fail—upload the .mmdb file manually to wp-content/uploads/wp-slimstat/." msgstr "" -#: admin/config/index.php:177 -msgid "Choose a service to update the GeoIP database to ensure your geographic information is accurate and up-to-date." +#: admin/config/index.php:297 +msgid "Geolocation Database" msgstr "" -#: admin/config/index.php:177 -msgid "<b>Note: </b>If the database file is missing, it will be downloaded when you save the settings." +#: admin/config/index.php:298 +msgid "An error occurred while updating the GeoIP database." msgstr "" -#: admin/config/index.php:180 -msgid "MaxMind License Key" +#: admin/config/index.php:298 +msgid "Update Database" msgstr "" -#: admin/config/index.php:182 -msgid "To be able to automatically download and update the MaxMind GeoLite2 database, you must sign up on <a href=\"https://dev.maxmind.com/geoip/geoip2/geolite2/\" target=\"_blank\">MaxMind GeoLite2</a> and create a license key. Then enter your license key in this field. Disable- and re-enable MaxMind Geolocation above to activate the license key. Note: It takes a couple of minutes after you created the license key to get it activated on the MaxMind website." +#: admin/config/index.php:298 +msgid "Check Database" msgstr "" -#: admin/config/index.php:185 +#: admin/config/index.php:300 +msgid "Download or refresh the selected geolocation database. <strong>DB-IP/MaxMind only</strong>: \"Update Database\" runs it now; after saving settings, Slimstat also schedules a background update. \"Check Database\" verifies that the file exists and is readable. <strong>Cloudflare</strong>: No database is required—the header is used at request time." +msgstr "" + +#: admin/config/index.php:303 +#: admin/config/index.php:385 msgid "Browscap Library" msgstr "" -#: admin/config/index.php:187 +#: admin/config/index.php:305 +#: admin/config/index.php:387 msgid "We are contributing to the <a href='https://browscap.org/' target='_blank'>Browscap Capabilities Project</a>, which we use to decode your visitors' user agent string into browser name and operating system. We use an <a href='https://github.com/slimstat/browscap-cache' target='_blank'>optimized version of their data structure</a>, for improved performance. When enabled, Slimstat uses this library in addition to the built-in heuristic function, to determine your visitors' browser information. Updates are downloaded automatically every week, when available." msgstr "" -#: admin/config/index.php:187 +#: admin/config/index.php:305 +#: admin/config/index.php:387 #, php-format msgid "You are currently using version %s." msgstr "" -#: admin/config/index.php:192 +#: admin/config/index.php:310 msgid "Advanced Options" msgstr "" -#: admin/config/index.php:196 +#: admin/config/index.php:314 msgid "Geolocation Precision" msgstr "" -#: admin/config/index.php:198 +#: admin/config/index.php:316 +#: src/Services/Privacy/DataExporter.php:135 msgid "Country" msgstr "" -#: admin/config/index.php:199 -#: admin/view/wp-slimstat-db.php:51 +#: admin/config/index.php:317 +#: admin/view/wp-slimstat-db.php:54 +#: src/Services/Privacy/DataExporter.php:142 msgid "City" msgstr "" -#: admin/config/index.php:200 -msgid "Slimstat determines your visitors' Country of origin through third-party libraries. This information is available in two precision levels: country and city. By default, Slimstat will install the country precision level. Use this option to switch to the more granular level, if you don't mind its 60 Mb average size." +#: admin/config/index.php:318 +msgid "Choose between Country and City precision. City uses a larger database and may take longer to download (and more disk space). Country is smaller and faster. Applies to DB‑IP and MaxMind; Cloudflare always provides country only." msgstr "" -#: admin/config/index.php:203 -#: admin/view/wp-slimstat-reports.php:387 +#: admin/config/index.php:321 +#: admin/view/wp-slimstat-reports.php:389 msgid "Visit Duration" msgstr "" -#: admin/config/index.php:205 -#: admin/config/index.php:306 +#: admin/config/index.php:323 +#: admin/config/index.php:456 +#: wp-slimstat.php:264 msgid "seconds" msgstr "" -#: admin/config/index.php:206 +#: admin/config/index.php:324 msgid "How many seconds should a human visit last? Google Analytics sets it to 1800 seconds." msgstr "" -#: admin/config/index.php:209 +#: admin/config/index.php:327 msgid "Extend Duration" msgstr "" -#: admin/config/index.php:211 +#: admin/config/index.php:329 msgid "Reset your visitors' visit duration every time they access a new page within the current visit." msgstr "" -#: admin/config/index.php:214 +#: admin/config/index.php:334 +msgid "Performance" +msgstr "" + +#: admin/config/index.php:338 msgid "Enable CDN" msgstr "" -#: admin/config/index.php:216 +#: admin/config/index.php:340 msgid "Use <a href='https://www.jsdelivr.com/' target='_blank'>JSDelivr</a>'s CDN, by serving our tracking code from their fast and reliable network (free service)." msgstr "" -#: admin/config/index.php:219 +#: admin/config/index.php:343 msgid "Relative Ajax" msgstr "" -#: admin/config/index.php:221 +#: admin/config/index.php:345 msgid "Try enabling this option if you are experiencing issues related to the header field X-Requested-With not being allowed by Access-Control-Allow-Headers in preflight response (or similar)." msgstr "" -#: admin/config/index.php:226 +#: admin/config/index.php:350 msgid "External Pages" msgstr "" -#: admin/config/index.php:230 +#: admin/config/index.php:354 msgid "Allowed Domains" msgstr "" -#: admin/config/index.php:232 +#: admin/config/index.php:356 msgid "If you are getting an error saying that no 'Access-Control-Allow-Origin' header is present on the requested resource, when using the external tracking code here above, list the domains (complete with scheme) you would like to allow. For example: <code>https://my.domain.ext</code> (no trailing slash). Please see <a href='https://www.w3.org/TR/cors/#security' target='_blank'>this W3 resource</a> for more information on the security implications of allowing CORS requests." msgstr "" -#: admin/config/index.php:236 +#: admin/config/index.php:360 msgid "Add the following code to all the non-WordPress pages you would like to track, right before the closing BODY tag. Please make sure to change the protocol of all the URLs to HTTPS, if you external site is using a secure channel." msgstr "" -#: admin/config/index.php:243 -#: admin/config/index.php:471 +#: admin/config/index.php:370 +msgid "GeoIP Database Source" +msgstr "" + +#: admin/config/index.php:373 +msgid "Disable" +msgstr "" + +#: admin/config/index.php:374 +msgid "Use the JsDelivr" +msgstr "" + +#: admin/config/index.php:375 +msgid "Use the MaxMind server with your own license key" +msgstr "" + +#: admin/config/index.php:377 +msgid "Choose a service to update the GeoIP database to ensure your geographic information is accurate and up-to-date." +msgstr "" + +#: admin/config/index.php:377 +msgid "<b>Note: </b>If the database file is missing, it will be downloaded when you save the settings." +msgstr "" + +#: admin/config/index.php:382 +msgid "To be able to automatically download and update the MaxMind GeoLite2 database, you must sign up on <a href=\"https://dev.maxmind.com/geoip/geoip2/geolite2/\" target=\"_blank\">MaxMind GeoLite2</a> and create a license key. Then enter your license key in this field. Disable- and re-enable MaxMind Geolocation above to activate the license key. Note: It takes a couple of minutes after you created the license key to get it activated on the MaxMind website." +msgstr "" + +#: admin/config/index.php:393 +#: admin/config/index.php:621 msgid "Reports" msgstr "" -#: admin/config/index.php:247 +#: admin/config/index.php:397 msgid "Functionality" msgstr "" -#: admin/config/index.php:251 +#: admin/config/index.php:401 msgid "Current Month" msgstr "" -#: admin/config/index.php:253 +#: admin/config/index.php:403 msgid "Determine what time window to use for the reports. Enable this option to default to the current month, disable it to use the past X number of days (see option here below). Use the date and time filters for a more granular analysis." msgstr "" -#: admin/config/index.php:256 +#: admin/config/index.php:406 msgid "Time Range" msgstr "" -#: admin/config/index.php:259 +#: admin/config/index.php:408 +#: admin/config/index.php:699 +#: admin/view/wp-slimstat-db.php:101 +msgid "days" +msgstr "" + +#: admin/config/index.php:409 msgid "Default number of days in the time window used to generate all the reports. We set it to 4 weeks so that the comparison charts will overlap nicely (i.e. Monday over Monday) for a more meaningful analysis. This value is ignored if the option here above is turned on." msgstr "" -#: admin/config/index.php:262 -#: admin/config/index.php:309 +#: admin/config/index.php:412 +#: admin/config/index.php:459 msgid "Rows to Display" msgstr "" -#: admin/config/index.php:264 -#: admin/config/index.php:312 -#: admin/config/index.php:358 +#: admin/config/index.php:414 +#: admin/config/index.php:462 +#: admin/config/index.php:508 msgid "rows" msgstr "" -#: admin/config/index.php:265 +#: admin/config/index.php:415 msgid "Define the number of rows to display in Top and Recent reports. You can adjust this number to improve your server performance." msgstr "" -#: admin/config/index.php:268 +#: admin/config/index.php:418 msgid "IP Geolocation" msgstr "" -#: admin/config/index.php:270 +#: admin/config/index.php:420 msgid "Customize the URL of the geolocation service to be used in the Access Log. Default value: <code>https://whatismyipaddress.com/ip/</code>" msgstr "" -#: admin/config/index.php:273 +#: admin/config/index.php:423 msgid "Comparison Chart" msgstr "" -#: admin/config/index.php:275 +#: admin/config/index.php:425 msgid "Slimstat displays two sets of charts, allowing you to compare the current time window with the previous one. Disable this option if you find those four charts confusing, and prefer seeing only the selected time range. Please keep in mind that you can always temporarily hide one series by clicking on the corresponding entry in the legend." msgstr "" -#: admin/config/index.php:278 +#: admin/config/index.php:428 msgid "Use Display Name" msgstr "" -#: admin/config/index.php:280 +#: admin/config/index.php:430 msgid "By default, users are listed by their usernames. Enable this option to show their display names instead." msgstr "" -#: admin/config/index.php:283 +#: admin/config/index.php:433 msgid "Display Titles" msgstr "" -#: admin/config/index.php:285 +#: admin/config/index.php:435 msgid "For improved legibility, most reports list post and page titles instead of their permalinks. Use this option to change this behavior." msgstr "" -#: admin/config/index.php:288 +#: admin/config/index.php:438 msgid "Display Hits" msgstr "" -#: admin/config/index.php:290 +#: admin/config/index.php:440 msgid "By default, Top and Recent reports display the percentage of pageviews compared to the total for each entry, and the actual number of hits on hover in a tooltip. Enable this feature if you prefer to see the number of hits directly and the percentage in the tooltip." msgstr "" -#: admin/config/index.php:293 +#: admin/config/index.php:443 msgid "Show Hostnames" msgstr "" -#: admin/config/index.php:295 +#: admin/config/index.php:445 msgid "Enable this option to display the hostname associated to each IP address. Please note that this might affect performance, as Slimstat will need to query your DNS server for each address." msgstr "" -#: admin/config/index.php:300 +#: admin/config/index.php:450 msgid "Access Log and World Map" msgstr "" -#: admin/config/index.php:304 +#: admin/config/index.php:454 msgid "Auto Refresh" msgstr "" -#: admin/config/index.php:307 +#: admin/config/index.php:457 msgid "When a value greater than zero is entered, the Access Log view will refresh every X seconds. Enter <strong>0</strong> (the number zero) if you would like to deactivate this feature." msgstr "" -#: admin/config/index.php:311 +#: admin/config/index.php:461 msgid "Define the number of rows to visualize in the Access Log." msgstr "" -#: admin/config/index.php:314 +#: admin/config/index.php:464 msgid "Map Data Points" msgstr "" -#: admin/config/index.php:316 +#: admin/config/index.php:466 msgid "Customize the maximum number of data points displayed on the world map. Please note that larger numbers might negatively affect rendering times." msgstr "" -#: admin/config/index.php:317 +#: admin/config/index.php:467 msgid "points" msgstr "" -#: admin/config/index.php:322 +#: admin/config/index.php:472 msgid "Miscellaneous" msgstr "" -#: admin/config/index.php:326 +#: admin/config/index.php:476 msgid "Custom CSS" msgstr "" -#: admin/config/index.php:330 +#: admin/config/index.php:480 msgid "Enter your own stylesheet definitions to customize the way your reports look. <a href='https://wp-slimstat.com/faq/how-can-i-change-the-colors-associated-to-color-coded-pageviews-known-user-known-visitors-search-engines-etc/' target='_blank'>Check our FAQs</a> for more information on how to use this option." msgstr "" -#: admin/config/index.php:333 +#: admin/config/index.php:483 msgid "Chart Colors" msgstr "" -#: admin/config/index.php:335 +#: admin/config/index.php:485 msgid "Customize the look and feel of your charts by assigning your own colors to each metric. List four hex colors, in the following order: metric 1 previous, metric 2 previous, metric 1 current, metric 2 current. For example: <code>#ccc, #999, #bbcc44, #21759b</code>." msgstr "" -#: admin/config/index.php:337 +#: admin/config/index.php:487 msgid "Mozscape Access ID" msgstr "" -#: admin/config/index.php:339 +#: admin/config/index.php:489 msgid "Get accurate rankings for your website through the <a href=\"https://moz.com/community/join?redirect=/products/api/keys\" target=\"_blank\">Mozscape API</a>. Sign up for a free community account to get started. Then enter your personal identification code in this field." msgstr "" -#: admin/config/index.php:341 +#: admin/config/index.php:491 msgid "Mozscape Secret Key" msgstr "" -#: admin/config/index.php:343 +#: admin/config/index.php:493 msgid "This key is needed to query the Mozscape API (see option here above). Treat it like a password and do not share it with anyone, or they will be able to make API requests using your account." msgstr "" -#: admin/config/index.php:346 +#: admin/config/index.php:496 msgid "Show User Agent" msgstr "" -#: admin/config/index.php:348 +#: admin/config/index.php:498 msgid "Enable this option if you want to see the full user agent string when hovering over each browser icon in the Access Log and elsewhere." msgstr "" -#: admin/config/index.php:351 +#: admin/config/index.php:501 msgid "Async Mode" msgstr "" -#: admin/config/index.php:353 +#: admin/config/index.php:503 msgid "Activate this feature if your reports take a while to load. It breaks down the load on your server into multiple smaller requests, thus avoiding memory issues and performance problems." msgstr "" -#: admin/config/index.php:356 +#: admin/config/index.php:506 msgid "SQL Limit" msgstr "" -#: admin/config/index.php:359 +#: admin/config/index.php:509 msgid "You can limit the number of records that each SQL query will take into consideration when crunching aggregate values (maximum, average, etc). You might need to adjust this value if you're getting an error saying that you exceeded your PHP memory limit while accessing the slimstat." msgstr "" -#: admin/config/index.php:362 +#: admin/config/index.php:512 msgid "Enable SOV" msgstr "" -#: admin/config/index.php:364 +#: admin/config/index.php:514 msgid "In linguistic typology, a subject-object-verb (SOV) language is one in which the subject, object, and verb of a sentence appear in that order, like in Japanese." msgstr "" -#: admin/config/index.php:370 +#: admin/config/index.php:520 msgid "Exclusions" msgstr "" -#: admin/config/index.php:374 +#: admin/config/index.php:524 msgid "User Properties" msgstr "" -#: admin/config/index.php:378 +#: admin/config/index.php:528 msgid "WP Users" msgstr "" -#: admin/config/index.php:380 +#: admin/config/index.php:530 msgid "If enabled, logged in WordPress users will not be tracked, neither on the website nor in the backend." msgstr "" -#: admin/config/index.php:383 +#: admin/config/index.php:533 msgid "Spammers" msgstr "" -#: admin/config/index.php:385 +#: admin/config/index.php:535 msgid "If enabled, visits from users identified as spammers by third-party tools like Akismet will not be tracked. Pageviews generated by users whose comments are later marked as spam, will also be removed from the database on a daily basis." msgstr "" -#: admin/config/index.php:388 -#: admin/view/wp-slimstat-db.php:1203 +#: admin/config/index.php:538 +#: admin/view/wp-slimstat-db.php:1454 msgid "Bots" msgstr "" -#: admin/config/index.php:390 +#: admin/config/index.php:540 msgid "If enabled, pageviews generated by crawlers, spiders, search engine bots, and other automated tools will not be tracked. Please note that if the tracker is set to work in Client mode, some of those pageviews might not be tracked anyway, since these tools usually do not run any embedded Javascript code." msgstr "" -#: admin/config/index.php:393 +#: admin/config/index.php:543 msgid "Prefetch Requests" msgstr "" -#: admin/config/index.php:395 +#: admin/config/index.php:545 msgid "<a href=\"https://en.wikipedia.org/wiki/Link_prefetching\" target=\"_blank\">Link Prefetching</a> is a technique that allows web browsers to pre-load resources, before the user clicks on the corresponding link. If enabled, this kind of requests will not be tracked." msgstr "" -#: admin/config/index.php:398 -#: admin/config/index.php:486 -#: admin/config/index.php:503 -#: admin/config/index.php:520 +#: admin/config/index.php:548 +#: admin/config/index.php:636 +#: admin/config/index.php:653 +#: admin/config/index.php:670 msgid "Usernames" msgstr "" -#: admin/config/index.php:400 +#: admin/config/index.php:550 msgid "Enter a list of usernames that should not be tracked. Please note that spaces are <em>not</em> ignored and that usernames are case sensitive. See note at the bottom of this page for more information on how to use wildcards." msgstr "" -#: admin/config/index.php:403 +#: admin/config/index.php:553 msgid "Capabilities" msgstr "" -#: admin/config/index.php:405 +#: admin/config/index.php:555 msgid "Enter a list of <a href=\"https://wordpress.org/support/article/roles-and-capabilities/\" target=\"_new\">WordPress capabilities</a>, so that users who have any of them assigned to their role will not be tracked. Please note that although capabilities are case-insensitive, it is recommended to enter them all in lowercase. See note at the bottom of this page for more information on how to use wildcards." msgstr "" -#: admin/config/index.php:408 +#: admin/config/index.php:558 msgid "IP Addresses" msgstr "" -#: admin/config/index.php:410 +#: admin/config/index.php:560 msgid "Enter a list of IP addresses that should not be tracked. Each subnet <strong>must</strong> be defined using the <a href='https://www.iplocation.net/subnet-mask' target='_blank'>CIDR notation</a> (i.e. <em>192.168.0.0/24</em>). This filter applies both to the public IP address and the originating IP address, if available. Using the CIDR notation, you will use octets to determine the mask. For example, 54.0.0.0/8 matches any address that has 54 as the first number; 54.12.0.0/16 matches any address that starts with 54.12, and so on." msgstr "" -#: admin/config/index.php:413 +#: admin/config/index.php:563 msgid "Countries" msgstr "" -#: admin/config/index.php:415 +#: admin/config/index.php:565 msgid "Enter a list of lowercase <a href=\"https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2\" target=\"_blank\">ISO 3166-1 country codes</a> (i.e.: <code>us, it, es</code>) that should not be tracked. Please note: this field does not allow wildcards." msgstr "" -#: admin/config/index.php:418 +#: admin/config/index.php:568 msgid "Languages" msgstr "" -#: admin/config/index.php:420 +#: admin/config/index.php:570 msgid "Enter a list of lowercase <a href=\"http://www.lingoes.net/en/translator/langcode.htm\" target=\"_blank\">ISO 639-1 language codes</a> (i.e.: <code>en-us, fr-ca, zh-cn</code>) that should not be tracked. Please note: this field does not allow wildcards." msgstr "" -#: admin/config/index.php:423 +#: admin/config/index.php:573 msgid "User Agents" msgstr "" -#: admin/config/index.php:425 +#: admin/config/index.php:575 msgid "Enter a list of browser names that should not be tracked. You can specify the browser's version adding a slash after the name (i.e. <em>Firefox/36</em>). Technically speaking, Slimstat will match your list against the visitor's user agent string. Strings are case-insensitive. See note at the bottom of this page for more information on how to use wildcards." msgstr "" -#: admin/config/index.php:428 +#: admin/config/index.php:578 msgid "Operating Systems" msgstr "" -#: admin/config/index.php:430 +#: admin/config/index.php:580 msgid "Enter a list of operating system codes that should not be tracked. Please refer to <a href=\"https://wp-slimstat.com/knowledge-base/\" target=\"_blank\">this page</a> in our knowledge base to learn more about which codes can be used. See note at the bottom of this page for more information on how to use wildcards." msgstr "" -#: admin/config/index.php:435 +#: admin/config/index.php:585 msgid "Page Properties" msgstr "" -#: admin/config/index.php:439 +#: admin/config/index.php:589 msgid "Permalinks" msgstr "" -#: admin/config/index.php:441 +#: admin/config/index.php:591 msgid "Enter a list of permalinks that should not be tracked. Do not include your website domain name: <code>/about, ?p=1</code>, etc. See note at the bottom of this page for more information on how to use wildcards. Strings are case-insensitive." msgstr "" -#: admin/config/index.php:444 +#: admin/config/index.php:594 msgid "Link Attributes: class names, REL and HREF" msgstr "" -#: admin/config/index.php:446 +#: admin/config/index.php:596 msgid "Do not track events on page elements whose class names, <em>rel</em> attributes or <em>href</em> attribute contain one of the following strings. Please keep in mind that the class <code>noslimstat</code> is used to avoid tracking interactive links throughout the reports. If you remove it from this list, some features might not work as expected." msgstr "" -#: admin/config/index.php:449 +#: admin/config/index.php:599 msgid "Referring Sites" msgstr "" -#: admin/config/index.php:451 +#: admin/config/index.php:601 msgid "Enter a list of referring URLs that should not be tracked: <code>https://mysite.com*</code>, <code>*/ignore-me-please</code>, etc. See note at the bottom of this page for more information on how to use wildcards. Strings are case-insensitive and must include the protocol (https://, https://)." msgstr "" -#: admin/config/index.php:454 +#: admin/config/index.php:604 msgid "Content Types" msgstr "" -#: admin/config/index.php:456 +#: admin/config/index.php:606 msgid "Enter a list of Slimstat content types that should not be tracked: <code>post, page, attachment, tag, 404, taxonomy, author, archive, search, feed, login</code>, etc. See note at the bottom of this page for more information on how to use wildcards. String should be entered in lowercase." msgstr "" -#: admin/config/index.php:460 +#: admin/config/index.php:610 msgid "<strong>Wildcards</strong><br>You can use the character <code>*</code> to match <em>any string, including the empty string</em>, and the character <code>!</code> to match <em>any character, including no character</em>. For example, <code>user*</code> matches user12 and userfoo, <code>u*100</code> matches user100 and ur100, <code>user!0</code> matches user10, user0 and user90, but not user100." msgstr "" -#: admin/config/index.php:467 +#: admin/config/index.php:617 msgid "Access Control" msgstr "" -#: admin/config/index.php:475 +#: admin/config/index.php:625 msgid "Restrict Authors" msgstr "" -#: admin/config/index.php:477 +#: admin/config/index.php:627 msgid "Enable this option if you want your authors to only see slimstat related to their own content." msgstr "" -#: admin/config/index.php:480 -#: admin/config/index.php:497 -#: admin/config/index.php:514 +#: admin/config/index.php:630 +#: admin/config/index.php:647 +#: admin/config/index.php:664 msgid "Minimum Capability" msgstr "" -#: admin/config/index.php:483 +#: admin/config/index.php:633 msgid "Specify the minimum <a href='https://wordpress.org/support/article/roles-and-capabilities/' target='_new'>capability</a> your WordPress users must have to have to access the reports (default: <code>manage_options</code>). The field here below can be used to override this option for specific users." msgstr "" -#: admin/config/index.php:488 +#: admin/config/index.php:638 msgid "Enter a list of usernames who should have access to the slimstat. Administrators are implicitly allowed, so you don't need to list them here below. Usernames are case sensitive. Wildcards are not allowed." msgstr "" -#: admin/config/index.php:493 +#: admin/config/index.php:643 msgid "Customizer" msgstr "" -#: admin/config/index.php:500 +#: admin/config/index.php:650 msgid "Specify the minimum <a href='https://wordpress.org/support/article/roles-and-capabilities/' target='_new'>capability</a> your WordPress users must have to access the Customizer (default: <code>manage_options</code>). The field here below can be used to override this option for specific users." msgstr "" -#: admin/config/index.php:505 +#: admin/config/index.php:655 msgid "Enter a list of usernames who should have access to the customizer. Administrators are implicitly allowed, so you don't need to list them here below. Usernames are case sensitive. Wildcards are not allowed." msgstr "" -#: admin/config/index.php:510 -#: admin/config/index.php:581 -#: admin/config/index.php:779 -#: admin/index.php:93 +#: admin/config/index.php:660 +#: admin/config/index.php:748 +#: admin/index.php:96 +#: admin/view/partials/header.php:131 msgid "Settings" msgstr "" -#: admin/config/index.php:517 +#: admin/config/index.php:667 msgid "Specify the minimum <a href='https://wordpress.org/support/article/roles-and-capabilities/' target='_new'>capability</a> your WordPress users must have to configure Slimstat (default: <code>manage_options</code>). The field here below can be used to override this option for specific users." msgstr "" -#: admin/config/index.php:522 +#: admin/config/index.php:672 msgid "Enter a list of usernames who should have access to the plugin settings. Please be advised that administrators <strong>are not</strong> implicitly allowed, so do not forget to include yourself! Usernames are case sensitive. Wildcards are not allowed." msgstr "" -#: admin/config/index.php:527 +#: admin/config/index.php:677 msgid "REST API" msgstr "" -#: admin/config/index.php:531 +#: admin/config/index.php:681 msgid "Tokens" msgstr "" -#: admin/config/index.php:533 +#: admin/config/index.php:683 msgid "In order to send requests to the Slimstat REST API, you will need to pass a valid token to the endpoint (param ?token=XXX). Using the field here below, you can define as many tokens as you like, and distribute them to your API users. Please note: treat these tokens as passwords, as they will grant read access to your reports to anyone who knows them. Use a service like <a href='https://randomkeygen.com/#ci_key' target='_blank'>RandomKeyGen.com</a> to generate unique secure tokens." msgstr "" -#: admin/config/index.php:539 +#: admin/config/index.php:689 msgid "Maintenance" msgstr "" -#: admin/config/index.php:543 +#: admin/config/index.php:693 +msgid "Data Retention & Auto-Purge" +msgstr "" + +#: admin/config/index.php:697 +msgid "Retention Period" +msgstr "" + +#: admin/config/index.php:700 +msgid "<strong>GDPR Compliance:</strong> Automatically purge data older than the specified number of days. This process runs twice daily via WordPress cron to keep your database clean and maintain GDPR compliance.<br/><br/><strong>Recommended:</strong> <strong>420 days (14 months)</strong> - Complies with ePrivacy Directive and most GDPR interpretations. This ensures data is automatically removed after a reasonable retention period.<br/><strong>Warning:</strong> Retaining data longer than 14 months may require additional legal justification and a clear Data Processing Agreement (DPA) under GDPR Article 5(1)(e) (Storage Limitation Principle). Failing to comply can result in significant fines.<br/><br/>Set to <strong>0</strong> to disable automatic purging (<strong>strongly discouraged</strong> for GDPR compliance, as unlimited retention requires a very strong and documented legal justification)." +msgstr "" + +#: admin/config/index.php:703 +msgid "Archive Mode" +msgstr "" + +#: admin/config/index.php:705 +msgid "<strong>How to handle old data:</strong><br/><br/><strong>Enabled (Archive):</strong> Old records are moved to separate archive tables (<code>wp_slim_stats_archive</code>, <code>wp_slim_events_archive</code>) instead of being permanently deleted. This improves query performance by keeping the main tables smaller, while still allowing you to access historical data if needed. <strong>Note:</strong> Archived data still counts as data retention under GDPR requirements.<br/><br/><strong>Disabled (Delete):</strong> Old records are permanently deleted from the database. This is the most GDPR-compliant approach and frees up database space immediately. <strong>Warning:</strong> Deleted data cannot be recovered.<br/><br/><strong>Important:</strong> Archive tables are <strong>permanently deleted</strong> when you uninstall SlimStat. Always <strong>backup your data</strong> before uninstalling if you need to retain it." +msgstr "" + +#: admin/config/index.php:710 msgid "Troubleshooting" msgstr "" -#: admin/config/index.php:547 +#: admin/config/index.php:714 msgid "Tracker Error" msgstr "" -#: admin/config/index.php:549 -#: admin/config/index.php:555 -#: admin/config/index.php:612 -#: admin/config/index.php:617 +#: admin/config/index.php:716 +#: admin/config/index.php:722 +#: admin/config/index.php:780 +#: admin/config/index.php:785 msgid "So far so good." msgstr "" -#: admin/config/index.php:549 -#: admin/config/index.php:555 +#: admin/config/index.php:716 +#: admin/config/index.php:722 msgid "Reset this error" msgstr "" -#: admin/config/index.php:550 +#: admin/config/index.php:717 msgid "The information here above is useful to troubleshoot issues with the tracker. <strong>Errors</strong> are returned when the tracker could not record a page view for some reason, and are indicative of some kind of malfunction." msgstr "" -#: admin/config/index.php:553 +#: admin/config/index.php:720 msgid "GeoIP Database Error" msgstr "" -#: admin/config/index.php:556 +#: admin/config/index.php:723 msgid "The information here above is useful to troubleshoot issues with the GeoIP Database. <strong>Errors</strong> are returned when the GeoIP Database can't update or retrieve a visitor's location, indicating some malfunction." msgstr "" -#: admin/config/index.php:559 +#: admin/config/index.php:726 msgid "SQL Debug" msgstr "" -#: admin/config/index.php:561 +#: admin/config/index.php:728 msgid "Enable this option to display the SQL code associated to each report. This can be useful to troubleshoot issues with data consistency or missing pageviews." msgstr "" -#: admin/config/index.php:564 +#: admin/config/index.php:731 msgid "Increase Performance" msgstr "" -#: admin/config/index.php:566 +#: admin/config/index.php:733 msgid "Enable this option to add column indexes to the main Slimstat table. This will make SQL queries faster and increase the size of the table by about 30%." msgstr "" -#: admin/config/index.php:571 +#: admin/config/index.php:738 msgid "Danger Zone" msgstr "" -#: admin/config/index.php:575 +#: admin/config/index.php:742 msgid "Data" msgstr "" -#: admin/config/index.php:577 +#: admin/config/index.php:744 msgid "Please confirm that you want to PERMANENTLY DELETE ALL the records from your database." msgstr "" -#: admin/config/index.php:577 +#: admin/config/index.php:744 msgid "Delete Records" msgstr "" -#: admin/config/index.php:578 +#: admin/config/index.php:745 msgid "Delete all the information collected by Slimstat so far, but not the archived records (stored in <code>wp_slim_stats_archive</code>). This operation <strong>does not</strong> reset your settings and it can be undone by manually copying your records from the archive table, if you have the corresponding option enabled." msgstr "" -#: admin/config/index.php:583 +#: admin/config/index.php:750 msgid "Please confirm that you want to RESET your settings." msgstr "" -#: admin/config/index.php:583 +#: admin/config/index.php:750 msgid "Factory Reset" msgstr "" -#: admin/config/index.php:584 +#: admin/config/index.php:751 msgid "Restore all the settings to their default value. This action DOES NOT delete any records collected by the plugin." msgstr "" -#: admin/config/index.php:587 +#: admin/config/index.php:754 msgid "Delete Data on Uninstall" msgstr "" -#: admin/config/index.php:589 +#: admin/config/index.php:756 msgid "Delete all settings and slimstat on plugin uninstall. Warning! If you enable this feature, all slimstat and plugin settings will be permanently deleted from the database." msgstr "" -#: admin/config/index.php:595 +#: admin/config/index.php:762 msgid "Pro Options" msgstr "" -#: admin/config/index.php:599 +#: admin/config/index.php:766 msgid "License" msgstr "" -#: admin/config/index.php:623 +#: admin/config/index.php:791 msgid "All settings were successfully reset to their default values." msgstr "" -#: admin/config/index.php:631 +#: admin/config/index.php:799 msgid "All your records were successfully deleted." msgstr "" -#: admin/config/index.php:640 +#: admin/config/index.php:808 msgid "Insufficient permissions." msgstr "" -#: admin/config/index.php:647 -#: admin/index.php:311 +#: admin/config/index.php:815 +#: admin/index.php:435 +#: src/Migration/Admin/MigrationAdmin.php:154 msgid "Sorry, you are not allowed to access this page." msgstr "" -#: admin/config/index.php:656 +#: admin/config/index.php:824 msgid "Congratulations! Slimstat Analytics is now optimized for <a href=\"https://www.youtube.com/watch?v=ygE01sOhzz0\" target=\"_blank\">ludicrous speed</a>." msgstr "" -#: admin/config/index.php:664 +#: admin/config/index.php:832 msgid "Table indexes have been disabled. Enjoy the extra database space!" msgstr "" -#: admin/config/index.php:687 -msgid "The geolocation database has been installed on your server." +#: admin/config/index.php:865 +msgid "The geolocation database update has been scheduled in the background. You can also use the Update Database button below to start it now." msgstr "" -#: admin/config/index.php:715 +#: admin/config/index.php:883 msgid "The Browscap data file has been uninstalled from your server." msgstr "" -#: admin/config/index.php:718 +#: admin/config/index.php:886 msgid "There was an error deleting the Browscap data folder on your server. Please check your permissions." msgstr "" -#: admin/config/index.php:760 +#: admin/config/index.php:944 msgid "Your new settings have been saved." msgstr "" -#: admin/config/index.php:785 -#: admin/view/index.php:14 +#: admin/config/index.php:967 +msgid "Performance Notice:" +msgstr "" + +#: admin/config/index.php:968 +#, php-format +msgid "The following DB indexes are missing and should be created for optimal performance: %s. Please visit the Slimstat settings or re-activate the plugin to trigger index creation." +msgstr "" + +#: admin/config/index.php:989 +#: admin/view/index.php:13 msgid "<strong>AdBlock browser extension detected</strong> - If you see this notice, it means that your browser is not loading our stylesheet and/or Javascript files correctly. This could be caused by an overzealous ad blocker feature enabled in your browser (AdBlock Plus and friends). <a href=\"https://wp-slimstat.com/resources/the-reports-are-not-being-rendered-correctly-or-buttons-do-not-work\" target=\"_blank\">Please make sure to add an exception</a> to your configuration and allow the browser to load these assets." msgstr "" -#: admin/config/index.php:843 +#: admin/config/index.php:1060 msgid "On" msgstr "" -#: admin/config/index.php:844 +#: admin/config/index.php:1061 msgid "Off" msgstr "" -#: admin/config/index.php:921 +#: admin/config/index.php:1165 msgid "Save Changes" msgstr "" -#: admin/index.php:51 +#: admin/index.php:47 msgid "Real-time" msgstr "" -#: admin/index.php:58 +#: admin/index.php:54 msgid "Overview" msgstr "" -#: admin/index.php:65 +#: admin/index.php:61 msgid "Audience" msgstr "" -#: admin/index.php:72 +#: admin/index.php:68 msgid "Site Analysis" msgstr "" -#: admin/index.php:79 +#: admin/index.php:75 #: admin/view/wp-slimstat-reports.php:82 -#: admin/view/wp-slimstat-reports.php:545 +#: admin/view/wp-slimstat-reports.php:547 msgid "Traffic Sources" msgstr "" -#: admin/index.php:86 +#: admin/index.php:82 +msgid "Email Report" +msgstr "" + +#: admin/index.php:82 +msgid "Email Report (pro)" +msgstr "" + +#: admin/index.php:89 +#: admin/view/layout.php:10 msgid "Customize" msgstr "" -#: admin/index.php:100 +#: admin/index.php:103 msgid "Upgrade to Pro" msgstr "" -#: admin/index.php:107 +#: admin/index.php:110 msgid "WordPress Dashboard" msgstr "" -#: admin/index.php:690 -#: admin/index.php:771 -msgid "Slimstat" +#: admin/index.php:949 +#: admin/index.php:963 +msgid "SlimStat" +msgstr "" + +#: admin/index.php:1172 +#: src/Modules/Chart.php:132 +msgid "Now" +msgstr "" + +#: admin/index.php:1173 +msgid "min ago" msgstr "" -#: admin/index.php:901 +#: admin/index.php:1185 +#: admin/index.php:1214 +msgid "Online Users" +msgstr "" + +#: admin/index.php:1186 +msgid "Count" +msgstr "" + +#: admin/index.php:1199 +#, php-format +msgid "Online: %s" +msgstr "" + +#: admin/index.php:1219 +msgid "Realtime" +msgstr "" + +#: admin/index.php:1223 +msgid "Visitors Today" +msgstr "" + +#: admin/index.php:1226 +#: admin/index.php:1233 +#: admin/index.php:1240 +#, php-format +msgid "was %s last day" +msgstr "" + +#: admin/index.php:1230 +msgid "Views Today" +msgstr "" + +#: admin/index.php:1237 +msgid "Referrals Today" +msgstr "" + +#: admin/index.php:1268 +#: views/reports/live-analytics.php:184 +msgid "Unlock the Full Power of SlimStat Analytics" +msgstr "" + +#: admin/index.php:1271 +#: admin/view/partials/slimstat-pro-modal.php:101 +#: views/reports/live-analytics.php:192 +msgid "Unlock SlimStat Pro" +msgstr "" + +#: admin/index.php:1291 +msgid "Explore Details" +msgstr "" + +#: admin/index.php:1417 #, php-format msgid "Pageviews in the last %s days" msgstr "" -#: admin/index.php:903 +#: admin/index.php:1419 #, php-format msgid "Unique IPs in the last %s days" msgstr "" -#: admin/index.php:1090 +#: admin/index.php:1587 msgid "Already saved" msgstr "" -#: admin/index.php:1098 +#: admin/index.php:1595 msgid "Saved" msgstr "" -#: admin/index.php:1121 +#: admin/index.php:1618 msgid "Delete this filter" msgstr "" -#: admin/index.php:1183 +#: admin/index.php:2012 +#: admin/index.php:2051 +#: src/Migration/Admin/MigrationAdmin.php:227 +#: src/Migration/Admin/MigrationAdmin.php:273 +#: src/Migration/Admin/MigrationAdmin.php:300 +#: wp-slimstat.php:1557 +msgid "Permission denied" +msgstr "" + +#: admin/index.php:2019 +msgid "Cloudflare geolocation does not require a database." +msgstr "" + +#: admin/index.php:2028 +#: src/Services/GeoService.php:110 +msgid "GeoIP Database Successfully Updated!" +msgstr "" + +#: admin/index.php:2031 +#: src/Services/GeoService.php:110 +msgid "Failed to update GeoIP Database." +msgstr "" + +#: admin/index.php:2033 +msgid "Please check your MaxMind license key and try again." +msgstr "" + +#: admin/index.php:2037 +#, php-format +msgid "Details: %s" +msgstr "" + +#: admin/index.php:2058 +msgid "Cloudflare geolocation is active. No database to check." +msgstr "" + +#: admin/index.php:2062 +msgid "GeoIP Database is present and ready." +msgstr "" + +#: admin/index.php:2062 +msgid "GeoIP Database not found." +msgstr "" + +#: admin/index.php:2085 msgid "Definitions" msgstr "" -#: admin/index.php:1185 +#: admin/index.php:2087 msgid "Pageview" msgstr "" -#: admin/index.php:1185 +#: admin/index.php:2087 msgid "A request to load a single HTML file (\"page\"). This should be contrasted with a \"hit\", which refers to a request for any file from a web server. Slimstat logs a pageview each time the tracking code is executed" msgstr "" -#: admin/index.php:1186 +#: admin/index.php:2088 msgid "(Human) Visit" msgstr "" -#: admin/index.php:1186 +#: admin/index.php:2088 msgid "A period of interaction between a visitor's browser and your website, ending when the browser is closed or when the user has been inactive on that site for 30 minutes" msgstr "" -#: admin/index.php:1187 +#: admin/index.php:2089 msgid "Known Visitor" msgstr "" -#: admin/index.php:1187 +#: admin/index.php:2089 msgid "Any user who has left a comment on your blog, and is thus identified by WordPress as a returning visitor" msgstr "" -#: admin/index.php:1188 +#: admin/index.php:2090 msgid "Unique IP" msgstr "" -#: admin/index.php:1188 +#: admin/index.php:2090 msgid "Used to differentiate between multiple requests to download a file from one internet address (IP) and requests originating from many distinct addresses; since this measurement looks only at the internet address a pageview came from, it is useful, but not perfect" msgstr "" -#: admin/index.php:1189 -#: admin/index.php:1227 -#: admin/view/right-now.php:173 -#: admin/view/wp-slimstat-db.php:57 +#: admin/index.php:2091 +#: admin/index.php:2129 +#: admin/view/right-now.php:184 +#: admin/view/wp-slimstat-db.php:60 msgid "Originating IP" msgstr "" -#: admin/index.php:1189 +#: admin/index.php:2091 msgid "the originating IP address of a client connecting to a web server through an HTTP proxy or load balancer" msgstr "" -#: admin/index.php:1190 +#: admin/index.php:2092 msgid "Direct Traffic" msgstr "" -#: admin/index.php:1190 +#: admin/index.php:2092 msgid "All those people showing up to your Web site by typing in the URL of your Web site coming or from a bookmark; some people also call this \"default traffic\" or \"ambient traffic\"" msgstr "" -#: admin/index.php:1191 +#: admin/index.php:2093 msgid "Search Engine" msgstr "" -#: admin/index.php:1191 +#: admin/index.php:2093 msgid "Google, Yahoo, MSN, Ask, others; this bucket will include both your organic as well as your paid (PPC/SEM) traffic, so be aware of that" msgstr "" -#: admin/index.php:1192 -#: admin/index.php:1207 -#: admin/view/right-now.php:230 -#: admin/view/wp-slimstat-db.php:35 +#: admin/index.php:2094 +#: admin/index.php:2109 +#: admin/view/right-now.php:250 +#: admin/view/wp-slimstat-db.php:38 #: admin/view/wp-slimstat-reports.php:73 -#: admin/view/wp-slimstat-reports.php:268 -#: admin/view/wp-slimstat-reports.php:278 +#: admin/view/wp-slimstat-reports.php:270 +#: admin/view/wp-slimstat-reports.php:280 +#: views/modules/chart-view.php:92 msgid "Search Terms" msgstr "" -#: admin/index.php:1192 -#: admin/index.php:1207 +#: admin/index.php:2094 +#: admin/index.php:2109 msgid "Keywords used by your visitors to find your website on a search engine" msgstr "" -#: admin/index.php:1193 +#: admin/index.php:2095 msgid "SERP" msgstr "" -#: admin/index.php:1193 +#: admin/index.php:2095 msgid "Short for search engine results page, the Web page that a search engine returns with the results of its search. The value shown represents your rank (or position) within that list of results" msgstr "" -#: admin/index.php:1194 -#: admin/view/wp-slimstat-db.php:50 +#: admin/index.php:2096 +#: admin/view/wp-slimstat-db.php:53 +#: src/Services/Privacy/DataExporter.php:105 msgid "User Agent" msgstr "" -#: admin/index.php:1194 +#: admin/index.php:2096 msgid "Any program used for accessing a website; this includes browsers, robots, spiders and any other program that was used to retrieve information from the site" msgstr "" -#: admin/index.php:1195 -#: admin/view/wp-slimstat-db.php:42 +#: admin/index.php:2097 +#: admin/view/wp-slimstat-db.php:45 msgid "Outbound Link" msgstr "" -#: admin/index.php:1195 +#: admin/index.php:2097 msgid "A link from one domain to another is said to be outbound from its source anchor and inbound to its target. This report lists all the links to other websites followed by your visitors." msgstr "" -#: admin/index.php:1202 +#: admin/index.php:2104 msgid "Basic Filters" msgstr "" -#: admin/index.php:1204 -#: admin/view/wp-slimstat-db.php:32 +#: admin/index.php:2106 +#: admin/view/wp-slimstat-db.php:35 +#: src/Services/Privacy/DataExporter.php:113 msgid "Browser" msgstr "" -#: admin/index.php:1204 +#: admin/index.php:2106 msgid "User agent (Firefox, Chrome, ...)" msgstr "" -#: admin/index.php:1205 -#: admin/view/wp-slimstat-db.php:33 +#: admin/index.php:2107 +#: admin/view/wp-slimstat-db.php:36 msgid "Country Code" msgstr "" -#: admin/index.php:1205 +#: admin/index.php:2107 msgid "2-letter code (us, ru, de, it, ...)" msgstr "" -#: admin/index.php:1206 -#: admin/view/wp-slimstat-reports.php:1466 +#: admin/index.php:2108 +#: admin/view/wp-slimstat-reports.php:1535 msgid "IP" msgstr "" -#: admin/index.php:1206 +#: admin/index.php:2108 msgid "Visitor's public IP address" msgstr "" -#: admin/index.php:1208 +#: admin/index.php:2110 msgid "Language Code" msgstr "" -#: admin/index.php:1208 +#: admin/index.php:2110 msgid "Please refer to this <a target=\"_blank\" href=\"https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx\">language culture page</a> (first column) for more information" msgstr "" -#: admin/index.php:1209 -#: admin/view/wp-slimstat-db.php:37 +#: admin/index.php:2111 +#: admin/view/wp-slimstat-db.php:40 +#: src/Services/Privacy/DataExporter.php:120 msgid "Operating System" msgstr "" -#: admin/index.php:1209 +#: admin/index.php:2111 msgid "Accepts identifiers like win7, win98, macosx, ...; please refer to <a target=\"_blank\" href=\"https://php.net/manual/en/function.get-browser.php\">this manual page</a> for more information" msgstr "" -#: admin/index.php:1210 -#: admin/view/wp-slimstat-db.php:38 +#: admin/index.php:2112 +#: admin/view/wp-slimstat-db.php:41 msgid "Permalink" msgstr "" -#: admin/index.php:1210 +#: admin/index.php:2112 msgid "URL accessed on your site" msgstr "" -#: admin/index.php:1211 -#: admin/view/wp-slimstat-db.php:39 +#: admin/index.php:2113 +#: admin/view/wp-slimstat-db.php:42 msgid "Referer" msgstr "" -#: admin/index.php:1211 +#: admin/index.php:2113 msgid "Complete address of the referrer page" msgstr "" -#: admin/index.php:1212 +#: admin/index.php:2114 msgid "Visitor's Name" msgstr "" -#: admin/index.php:1212 +#: admin/index.php:2114 msgid "Visitors' names according to the cookie set by WordPress after they leave a comment" msgstr "" -#: admin/index.php:1220 +#: admin/index.php:2122 msgid "Advanced Filters" msgstr "" -#: admin/index.php:1222 -#: admin/view/wp-slimstat-db.php:48 +#: admin/index.php:2124 +#: admin/view/wp-slimstat-db.php:51 msgid "Browser Version" msgstr "" -#: admin/index.php:1222 +#: admin/index.php:2124 msgid "user agent version (9.0, 11, ...)" msgstr "" -#: admin/index.php:1223 -#: admin/view/wp-slimstat-db.php:49 +#: admin/index.php:2125 +#: admin/view/wp-slimstat-db.php:52 msgid "Browser Type" msgstr "" -#: admin/index.php:1223 +#: admin/index.php:2125 msgid "1 = search engine crawler, 2 = mobile device, 3 = syndication reader, 0 = all others" msgstr "" -#: admin/index.php:1224 +#: admin/index.php:2126 msgid "Pageview Attributes" msgstr "" -#: admin/index.php:1224 +#: admin/index.php:2126 msgid "this field is set to <em>[pre]</em> if the resource has been accessed through <a target=\"_blank\" href=\"https://developer.mozilla.org/en/Link_prefetching_FAQ\">Link Prefetching</a> or similar techniques" msgstr "" -#: admin/index.php:1225 -#: admin/view/wp-slimstat-db.php:55 +#: admin/index.php:2127 +#: admin/view/wp-slimstat-db.php:58 msgid "Post Author" msgstr "" -#: admin/index.php:1225 +#: admin/index.php:2127 msgid "author associated to that post/page when the resource was accessed" msgstr "" -#: admin/index.php:1226 -#: admin/view/wp-slimstat-db.php:56 +#: admin/index.php:2128 +#: admin/view/wp-slimstat-db.php:59 msgid "Post Category ID" msgstr "" -#: admin/index.php:1226 +#: admin/index.php:2128 msgid "ID of the category/term associated to the resource, when available" msgstr "" -#: admin/index.php:1227 +#: admin/index.php:2129 msgid "visitor's originating IP address, if available" msgstr "" -#: admin/index.php:1228 -#: admin/view/wp-slimstat-db.php:58 +#: admin/index.php:2130 +#: admin/view/wp-slimstat-db.php:61 msgid "Resource Content Type" msgstr "" -#: admin/index.php:1228 +#: admin/index.php:2130 msgid "post, page, cpt:<em>custom-post-type</em>, attachment, singular, post_type_archive, tag, taxonomy, category, date, author, archive, search, feed, home; please refer to the <a target=\"_blank\" href=\"https://codex.wordpress.org/Conditional_Tags\">Conditional Tags</a> manual page for more information" msgstr "" -#: admin/index.php:1229 +#: admin/index.php:2131 +#: src/Services/Privacy/DataExporter.php:127 msgid "Screen Resolution" msgstr "" -#: admin/index.php:1229 +#: admin/index.php:2131 msgid "viewport width and height (1024x768, 800x600, ...)" msgstr "" -#: admin/index.php:1230 -#: admin/view/wp-slimstat-db.php:63 +#: admin/index.php:2132 +#: admin/view/wp-slimstat-db.php:66 +#: src/Services/Privacy/DataExporter.php:150 msgid "Visit ID" msgstr "" -#: admin/index.php:1230 +#: admin/index.php:2132 msgid "generally used in conjunction with <em>is not empty</em>, identifies human visitors" msgstr "" -#: admin/index.php:1231 +#: admin/index.php:2133 msgid "Date Filters" msgstr "" -#: admin/index.php:1231 +#: admin/index.php:2133 msgid "you can specify the timeframe by entering a number in the <em>interval</em> field; use -1 to indicate <em>to date</em> (i.e., day=1, month=1, year=blank, interval=-1 will set a year-to-date filter)" msgstr "" -#: admin/index.php:1232 +#: admin/index.php:2134 msgid "SERP Position" msgstr "" -#: admin/index.php:1232 +#: admin/index.php:2134 msgid "set the filter to Referer contains cd=N&, where N is the position you are looking for" msgstr "" -#: admin/view/addons.php:23 -#, php-format -msgid "There was an error retrieving the add-ons list from the server. Please try again later. Error Message: %s" +#: admin/index.php:2278 +#: admin/index.php:2303 +#: admin/index.php:2328 +#: admin/index.php:2353 +#: admin/index.php:2379 +msgid "Index already exists." msgstr "" -#: admin/view/addons.php:33 -msgid "There was an error decoding the add-ons list from the server. Please try again later." +#: admin/index.php:2283 +#: admin/index.php:2308 +#: admin/index.php:2333 +#: admin/index.php:2358 +#: admin/index.php:2385 +msgid "Index added successfully." msgstr "" -#: admin/view/addons.php:38 -msgid "Add-ons" +#: admin/index.php:2285 +#: admin/index.php:2310 +#: admin/index.php:2335 +#: admin/index.php:2360 +#: admin/index.php:2387 +msgid "Unable to add index or it already exists." msgstr "" -#: admin/view/addons.php:39 -msgid "Add-ons extend the functionality of Slimstat in many interesting ways. We offer both free and premium (paid) extensions. Each add-on can be installed as a separate plugin, which will receive regular updates via the WordPress Plugins panel. In order to be notified when a new version of a premium add-on is available, please enter the <strong>license key</strong> you received when you purchased it." +#: admin/index.php:2409 +msgid "Currently Online Reports" msgstr "" -#: admin/view/addons.php:42 -#, php-format -msgid "This list is refreshed once daily: <a href=\"%s&force_refresh=true\" class=\"noslimstat\">click here</a> to clear the cache." +#: admin/index.php:2410 +msgid "Index on <code>dt_out</code>" msgstr "" -#: admin/view/addons.php:57 -msgid "Add-on" +#: admin/index.php:2413 +#: admin/index.php:2422 +#: admin/index.php:2431 +#: admin/index.php:2440 +#: admin/index.php:2449 +#: admin/view/index.php:39 +#: src/Components/DateRangeHelper.php:212 +msgid "Apply" msgstr "" -#: admin/view/addons.php:58 -msgid "Description" +#: admin/index.php:2418 +msgid "World Map & Country Reports" msgstr "" -#: admin/view/addons.php:69 -msgid "Repo Version" +#: admin/index.php:2419 +msgid "Index on <code>country</code> and <code>dt</code>" msgstr "" -#: admin/view/addons.php:69 -msgid "Version" +#: admin/index.php:2427 +msgid "Screen Resolution Reports" msgstr "" -#: admin/view/addons.php:80 -msgid "Your Version:" +#: admin/index.php:2428 +msgid "Index on <code>dt</code>, <code>screen_width</code>, <code>screen_height</code>" msgstr "" -#: admin/view/addons.php:82 -msgid "Installed and Active" +#: admin/index.php:2436 +msgid "Browser Reports" msgstr "" -#: admin/view/index.php:18 -msgid "Dimension" +#: admin/index.php:2437 +msgid "Index on <code>dt</code>, <code>browser</code>, <code>browser_version</code>" msgstr "" -#: admin/view/index.php:38 -#: admin/view/index.php:106 -msgid "Apply" +#: admin/index.php:2445 +msgid "Platform Reports" msgstr "" -#: admin/view/index.php:42 -msgid "Load" +#: admin/index.php:2446 +msgid "Index on <code>dt</code>, <code>platform</code>" msgstr "" -#: admin/view/index.php:66 -#: admin/view/wp-slimstat-db.php:812 -#: src/Modules/Chart.php:115 -msgid "Today" +#: admin/index.php:2470 +msgid "Improve SlimStat Report Performance" msgstr "" -#: admin/view/index.php:67 -#: admin/view/wp-slimstat-db.php:815 -msgid "Yesterday" +#: admin/index.php:2471 +msgid "To speed up SlimStat reports, please apply the following database optimizations. These changes are safe and will not affect your data." msgstr "" -#: admin/view/index.php:68 -msgid "Last 7 Days" +#: admin/index.php:2489 +msgid "Apply All" msgstr "" -#: admin/view/index.php:69 -msgid "Last 2 weeks" +#: admin/index.php:2490 +msgid "Do not close this tab until all optimizations are complete." msgstr "" -#: admin/view/index.php:70 -msgid "Last 4 weeks" +#: admin/index.php:2513 +msgid "Please wait for SlimStat optimizations to finish." msgstr "" -#: admin/view/index.php:71 -msgid "Last 12 weeks" +#: admin/index.php:2521 +msgid "In progress..." msgstr "" -#: admin/view/index.php:72 -msgid "Last 12 months" +#: admin/index.php:2529 +msgid "Done!" msgstr "" -#: admin/view/index.php:73 -msgid "This Month" +#: admin/index.php:2533 +msgid "Error: " msgstr "" -#: admin/view/index.php:74 -msgid "Previous Month" +#: admin/view/addons.php:23 +#, php-format +msgid "There was an error retrieving the add-ons list from the server. Please try again later. Error Message: %s" msgstr "" -#: admin/view/index.php:77 -msgid "Date Range" +#: admin/view/addons.php:34 +msgid "There was an error decoding the add-ons list from the server. Please try again later." msgstr "" -#: admin/view/index.php:79 -#: admin/view/index.php:80 -#: admin/view/wp-slimstat-db.php:94 -msgid "Hour" +#: admin/view/addons.php:39 +msgid "Add-ons" msgstr "" -#: admin/view/index.php:82 -#: admin/view/index.php:83 -#: admin/view/wp-slimstat-db.php:95 -msgid "Day" +#: admin/view/addons.php:40 +msgid "Add-ons extend the functionality of Slimstat in many interesting ways. We offer both free and premium (paid) extensions. Each add-on can be installed as a separate plugin, which will receive regular updates via the WordPress Plugins panel. In order to be notified when a new version of a premium add-on is available, please enter the <strong>license key</strong> you received when you purchased it." msgstr "" -#: admin/view/index.php:85 -#: admin/view/index.php:87 -#: admin/view/wp-slimstat-db.php:96 -msgid "Month" +#: admin/view/addons.php:43 +#, php-format +msgid "This list is refreshed once daily: <a href=\"%s&force_refresh=true\" class=\"noslimstat\">click here</a> to clear the cache." msgstr "" -#: admin/view/index.php:95 -#: admin/view/wp-slimstat-db.php:97 -msgid "Year" +#: admin/view/addons.php:58 +msgid "Add-on" msgstr "" -#: admin/view/index.php:100 -msgid "Days in interval" +#: admin/view/addons.php:59 +msgid "Description" msgstr "" -#: admin/view/index.php:101 -msgid "± days" +#: admin/view/addons.php:70 +msgid "Repo Version" msgstr "" -#: admin/view/index.php:101 -msgid "To define an interval, enter the number of days (negative to go back in time)." +#: admin/view/addons.php:70 +msgid "Version" msgstr "" -#: admin/view/index.php:103 -msgid "Hours in interval" +#: admin/view/addons.php:81 +msgid "Your Version:" msgstr "" -#: admin/view/index.php:104 -msgid "± hours" +#: admin/view/addons.php:83 +msgid "Installed and Active" +msgstr "" + +#: admin/view/email-report.php:36 +msgid "Email Report Configuration" +msgstr "" + +#: admin/view/index.php:17 +msgid "Dimension" msgstr "" -#: admin/view/index.php:114 -msgid "Reset Filters" +#: admin/view/index.php:43 +msgid "Saved Filters" msgstr "" -#: admin/view/index.php:138 -#: admin/view/wp-slimstat-reports.php:1728 +#: admin/view/index.php:104 +#: admin/view/index.php:107 +#: admin/view/wp-slimstat-reports.php:1761 #, php-format msgid "GeoIP collection is not enabled. Please go to <a href='%s' class='noslimstat'>setting page</a> to enable GeoIP for getting more information and location (country) from the visitor." msgstr "" -#: admin/view/index.php:142 +#: admin/view/index.php:112 #, php-format msgid "Install our <a href='%s' class='noslimstat'>Browscap Library</a> to identify your visitors' browser and operating system." msgstr "" -#: admin/view/index.php:147 +#: admin/view/index.php:117 #, php-format msgid "A caching plugin might be enabled on your website. Please <a href='%s' target='_blank' class='noslimstat'>make sure to configure</a> Slimstat Analytics accordingly, to get accurate information." msgstr "" -#: admin/view/layout.php:8 -msgid "Customize and organize your reports" -msgstr "" - -#: admin/view/layout.php:10 +#: admin/view/layout.php:14 msgid "You can drag and drop the placeholders here below from one widget area to another, to customize the layout of each report screen. You can place multiple charts on the same view, clone reports or move them to the Inactive Reports if you are not interested in that specific metric." msgstr "" -#: admin/view/layout.php:13 +#: admin/view/layout.php:17 msgid "By using the network-wide customizer, all your users will see the same layout you define, and they will not be able to customize it further." msgstr "" -#: admin/view/layout.php:23 +#: admin/view/layout.php:27 msgid "Reset Layout" msgstr "" -#: admin/view/layout.php:39 +#: admin/view/layout.php:43 msgid "Clone" msgstr "" -#: admin/view/layout.php:42 +#: admin/view/layout.php:46 msgid "Delete" msgstr "" -#: admin/view/layout.php:55 +#: admin/view/layout.php:59 msgid "Move to Inactive" msgstr "" -#: admin/view/partials/header.php:9 -msgid "Go PRO" +#: admin/view/partials/header.php:108 +msgid "Online Visitors" msgstr "" -#: admin/view/partials/header.php:10 -msgid "Upgrade to Pro to unlock more features" +#: admin/view/partials/header.php:116 +#: views/components/notification/side-bar.php:11 +msgid "Notifications" msgstr "" -#: admin/view/partials/header.php:17 -msgid "Pro is activated!" +#: admin/view/partials/header.php:127 +msgid "Help" +msgstr "" + +#: admin/view/partials/header.php:129 +msgid "Open help panel" +msgstr "" + +#: admin/view/partials/header.php:137 +msgid "Premium plan active" +msgstr "" + +#: admin/view/partials/header.php:143 +msgid "Premium" +msgstr "" + +#: admin/view/partials/header.php:146 +#: admin/view/partials/header.php:153 +msgid "Upgrade to Premium" msgstr "" #: admin/view/partials/slimstat-pro-modal.php:7 @@ -1550,501 +1832,527 @@ msgid "Add custom columns to the User Overview widget and export file." msgstr "" -#: admin/view/partials/slimstat-pro-modal.php:101 -msgid "Unlock SlimStat Pro" -msgstr "" - -#: admin/view/right-now.php:23 +#: admin/view/right-now.php:26 msgid "Human" msgstr "" -#: admin/view/right-now.php:23 +#: admin/view/right-now.php:26 msgid "Bot/Crawler" msgstr "" -#: admin/view/right-now.php:23 +#: admin/view/right-now.php:26 msgid "Mobile Device" msgstr "" -#: admin/view/right-now.php:23 +#: admin/view/right-now.php:26 msgid "Syndication Reader" msgstr "" -#: admin/view/right-now.php:49 -#: admin/view/wp-slimstat-reports.php:1087 -#: admin/view/wp-slimstat-reports.php:1443 -#: admin/view/wp-slimstat-reports.php:1501 +#: admin/view/right-now.php:52 +#: admin/view/wp-slimstat-reports.php:1140 +#: admin/view/wp-slimstat-reports.php:1512 +#: admin/view/wp-slimstat-reports.php:1570 msgid "No data to display" msgstr "" -#: admin/view/right-now.php:77 +#: admin/view/right-now.php:80 msgid "Date and Time" msgstr "" -#: admin/view/right-now.php:208 -#: admin/view/wp-slimstat-reports.php:1359 -#: admin/view/wp-slimstat-reports.php:1367 -#: admin/view/wp-slimstat-reports.php:1517 +#: admin/view/right-now.php:219 +#: admin/view/wp-slimstat-reports.php:1428 +#: admin/view/wp-slimstat-reports.php:1436 +#: admin/view/wp-slimstat-reports.php:1586 msgid "Open this URL in a new window" msgstr "" -#: admin/view/right-now.php:221 +#: admin/view/right-now.php:232 msgid "Local search results page" msgstr "" -#: admin/view/right-now.php:241 +#: admin/view/right-now.php:261 msgid "Server Latency and Page Speed in milliseconds" msgstr "" -#: admin/view/right-now.php:241 +#: admin/view/right-now.php:261 msgid "SL" msgstr "" -#: admin/view/right-now.php:241 +#: admin/view/right-now.php:261 msgid "PS" msgstr "" -#: admin/view/right-now.php:248 +#: admin/view/right-now.php:268 msgid "Time spent on this page" msgstr "" -#: admin/view/right-now.php:267 +#: admin/view/right-now.php:287 msgid "Invalid Referrer" msgstr "" -#: admin/view/right-now.php:268 +#: admin/view/right-now.php:288 msgid "Open this referrer in a new window" msgstr "" -#: admin/view/right-now.php:269 +#: admin/view/right-now.php:289 msgid "Content Type" msgstr "" -#: admin/view/right-now.php:274 +#: admin/view/right-now.php:294 msgid "Open this outbound link in a new window" msgstr "" -#: admin/view/right-now.php:283 +#: admin/view/right-now.php:303 msgid "Delete this entry from the database" msgstr "" -#: admin/view/right-now.php:295 +#: admin/view/right-now.php:315 msgid "User Logged In" msgstr "" -#: admin/view/right-now.php:306 +#: admin/view/right-now.php:326 msgid "User Logged Out" msgstr "" -#: admin/view/wp-slimstat-db.php:34 +#: admin/view/wp-slimstat-db.php:37 +#: src/Services/Privacy/DataExporter.php:91 msgid "IP Address" msgstr "" -#: admin/view/wp-slimstat-db.php:36 +#: admin/view/wp-slimstat-db.php:39 msgid "Language" msgstr "" -#: admin/view/wp-slimstat-db.php:40 +#: admin/view/wp-slimstat-db.php:43 msgid "Visitor's Username" msgstr "" -#: admin/view/wp-slimstat-db.php:41 +#: admin/view/wp-slimstat-db.php:44 msgid "Visitor's Email" msgstr "" -#: admin/view/wp-slimstat-db.php:43 +#: admin/view/wp-slimstat-db.php:46 msgid "Timezone Offset" msgstr "" -#: admin/view/wp-slimstat-db.php:44 +#: admin/view/wp-slimstat-db.php:47 msgid "Fingerprint" msgstr "" -#: admin/view/wp-slimstat-db.php:45 +#: admin/view/wp-slimstat-db.php:48 msgid "Page Speed" msgstr "" -#: admin/view/wp-slimstat-db.php:47 +#: admin/view/wp-slimstat-db.php:50 msgid "-- Advanced filters --" msgstr "" -#: admin/view/wp-slimstat-db.php:52 -#: admin/view/wp-slimstat-reports.php:1466 +#: admin/view/wp-slimstat-db.php:55 +#: admin/view/wp-slimstat-reports.php:1535 msgid "Coordinates" msgstr "" -#: admin/view/wp-slimstat-db.php:53 +#: admin/view/wp-slimstat-db.php:56 msgid "Annotations" msgstr "" -#: admin/view/wp-slimstat-db.php:54 +#: admin/view/wp-slimstat-db.php:57 msgid "Server Latency" msgstr "" -#: admin/view/wp-slimstat-db.php:59 +#: admin/view/wp-slimstat-db.php:62 msgid "Resource ID" msgstr "" -#: admin/view/wp-slimstat-db.php:60 +#: admin/view/wp-slimstat-db.php:63 msgid "Screen Width" msgstr "" -#: admin/view/wp-slimstat-db.php:61 +#: admin/view/wp-slimstat-db.php:64 msgid "Screen Height" msgstr "" -#: admin/view/wp-slimstat-db.php:62 +#: admin/view/wp-slimstat-db.php:65 msgid "Viewport Size" msgstr "" -#: admin/view/wp-slimstat-db.php:73 +#: admin/view/wp-slimstat-db.php:76 msgid "equals" msgstr "" -#: admin/view/wp-slimstat-db.php:74 +#: admin/view/wp-slimstat-db.php:77 msgid "is not equal to" msgstr "" -#: admin/view/wp-slimstat-db.php:75 +#: admin/view/wp-slimstat-db.php:78 msgid "contains" msgstr "" -#: admin/view/wp-slimstat-db.php:76 +#: admin/view/wp-slimstat-db.php:79 msgid "is included in" msgstr "" -#: admin/view/wp-slimstat-db.php:77 +#: admin/view/wp-slimstat-db.php:80 msgid "does not contain" msgstr "" -#: admin/view/wp-slimstat-db.php:78 +#: admin/view/wp-slimstat-db.php:81 msgid "starts with" msgstr "" -#: admin/view/wp-slimstat-db.php:79 +#: admin/view/wp-slimstat-db.php:82 msgid "ends with" msgstr "" -#: admin/view/wp-slimstat-db.php:80 +#: admin/view/wp-slimstat-db.php:83 msgid "sounds like" msgstr "" -#: admin/view/wp-slimstat-db.php:81 +#: admin/view/wp-slimstat-db.php:84 msgid "is greater than" msgstr "" -#: admin/view/wp-slimstat-db.php:82 +#: admin/view/wp-slimstat-db.php:85 msgid "is less than" msgstr "" -#: admin/view/wp-slimstat-db.php:83 +#: admin/view/wp-slimstat-db.php:86 msgid "is between (x,y)" msgstr "" -#: admin/view/wp-slimstat-db.php:84 +#: admin/view/wp-slimstat-db.php:87 msgid "matches" msgstr "" -#: admin/view/wp-slimstat-db.php:85 +#: admin/view/wp-slimstat-db.php:88 msgid "does not match" msgstr "" -#: admin/view/wp-slimstat-db.php:86 +#: admin/view/wp-slimstat-db.php:89 msgid "is empty" msgstr "" -#: admin/view/wp-slimstat-db.php:87 +#: admin/view/wp-slimstat-db.php:90 msgid "is not empty" msgstr "" -#: admin/view/wp-slimstat-db.php:93 +#: admin/view/wp-slimstat-db.php:96 msgid "Minute" msgstr "" +#: admin/view/wp-slimstat-db.php:97 +msgid "Hour" +msgstr "" + +#: admin/view/wp-slimstat-db.php:98 +msgid "Day" +msgstr "" + #: admin/view/wp-slimstat-db.php:99 -msgid "hours" +msgid "Month" msgstr "" #: admin/view/wp-slimstat-db.php:100 +msgid "Year" +msgstr "" + +#: admin/view/wp-slimstat-db.php:102 +msgid "hours" +msgstr "" + +#: admin/view/wp-slimstat-db.php:103 msgid "minutes" msgstr "" -#: admin/view/wp-slimstat-db.php:101 +#: admin/view/wp-slimstat-db.php:104 msgid "Timestamp" msgstr "" -#: admin/view/wp-slimstat-db.php:102 +#: admin/view/wp-slimstat-db.php:105 msgid "Exit Timestamp" msgstr "" -#: admin/view/wp-slimstat-db.php:105 +#: admin/view/wp-slimstat-db.php:108 msgid "Metric" msgstr "" -#: admin/view/wp-slimstat-db.php:106 +#: admin/view/wp-slimstat-db.php:109 msgid "Value" msgstr "" -#: admin/view/wp-slimstat-db.php:108 +#: admin/view/wp-slimstat-db.php:111 msgid "Grouped Value" msgstr "" -#: admin/view/wp-slimstat-db.php:109 +#: admin/view/wp-slimstat-db.php:112 msgid "Percentage" msgstr "" -#: admin/view/wp-slimstat-db.php:110 -#: admin/view/wp-slimstat-db.php:111 +#: admin/view/wp-slimstat-db.php:113 +#: admin/view/wp-slimstat-db.php:114 +#: src/Services/Privacy/DataExporter.php:240 msgid "Notes" msgstr "" -#: admin/view/wp-slimstat-db.php:114 +#: admin/view/wp-slimstat-db.php:117 msgid "Event ID" msgstr "" -#: admin/view/wp-slimstat-db.php:115 +#: admin/view/wp-slimstat-db.php:118 msgid "Type" msgstr "" -#: admin/view/wp-slimstat-db.php:116 +#: admin/view/wp-slimstat-db.php:119 +#: src/Services/Privacy/DataExporter.php:226 msgid "Event Description" msgstr "" -#: admin/view/wp-slimstat-db.php:117 +#: admin/view/wp-slimstat-db.php:120 msgid "Event Coordinates" msgstr "" -#: admin/view/wp-slimstat-db.php:119 +#: admin/view/wp-slimstat-db.php:122 msgid "Max Results" msgstr "" -#: admin/view/wp-slimstat-db.php:120 +#: admin/view/wp-slimstat-db.php:123 msgid "Offset" msgstr "" -#: admin/view/wp-slimstat-db.php:790 -#: admin/view/wp-slimstat-db.php:1071 +#: admin/view/wp-slimstat-db.php:1020 +#: admin/view/wp-slimstat-db.php:1322 #: admin/view/wp-slimstat-reports.php:46 #: admin/view/wp-slimstat-reports.php:115 #: admin/view/wp-slimstat-reports.php:124 msgid "Pageviews" msgstr "" -#: admin/view/wp-slimstat-db.php:792 -#: admin/view/wp-slimstat-db.php:1073 +#: admin/view/wp-slimstat-db.php:1022 +#: admin/view/wp-slimstat-db.php:1324 msgid "A pageview is a request to load a single HTML page on your website." msgstr "" -#: admin/view/wp-slimstat-db.php:794 +#: admin/view/wp-slimstat-db.php:1024 msgid "Days in Range" msgstr "" -#: admin/view/wp-slimstat-db.php:797 +#: admin/view/wp-slimstat-db.php:1027 msgid "Average Daily Pageviews" msgstr "" -#: admin/view/wp-slimstat-db.php:799 +#: admin/view/wp-slimstat-db.php:1029 msgid "How many daily pageviews have been generated on average." msgstr "" -#: admin/view/wp-slimstat-db.php:801 +#: admin/view/wp-slimstat-db.php:1031 msgid "From Any SERP" msgstr "" -#: admin/view/wp-slimstat-db.php:803 +#: admin/view/wp-slimstat-db.php:1033 msgid "Visitors who landed on your site after searching for a keyword on a search engine and clicking on the corresponding search result link. This value includes both internal and external search result pages." msgstr "" -#: admin/view/wp-slimstat-db.php:805 -#: admin/view/wp-slimstat-db.php:1187 +#: admin/view/wp-slimstat-db.php:1035 +#: admin/view/wp-slimstat-db.php:1438 #: admin/view/wp-slimstat-reports.php:125 -#: admin/view/wp-slimstat-reports.php:299 -#: admin/view/wp-slimstat-reports.php:556 +#: admin/view/wp-slimstat-reports.php:301 +#: admin/view/wp-slimstat-reports.php:558 msgid "Unique IPs" msgstr "" -#: admin/view/wp-slimstat-db.php:807 +#: admin/view/wp-slimstat-db.php:1037 msgid "Used to differentiate between multiple requests to download a file from one internet address (IP) and requests originating from many distinct addresses." msgstr "" -#: admin/view/wp-slimstat-db.php:809 +#: admin/view/wp-slimstat-db.php:1039 msgid "Last 30 minutes" msgstr "" -#: admin/view/wp-slimstat-db.php:1075 +#: admin/view/wp-slimstat-db.php:1042 +#: src/Components/DateRangeHelper.php:199 +#: src/Modules/Chart.php:128 +msgid "Today" +msgstr "" + +#: admin/view/wp-slimstat-db.php:1045 +#: src/Components/DateRangeHelper.php:200 +msgid "Yesterday" +msgstr "" + +#: admin/view/wp-slimstat-db.php:1326 msgid "Unique Referrers" msgstr "" -#: admin/view/wp-slimstat-db.php:1077 +#: admin/view/wp-slimstat-db.php:1328 msgid "A referrer (or referring site) is a site that a visitor previously visited before following a link to your site." msgstr "" -#: admin/view/wp-slimstat-db.php:1079 +#: admin/view/wp-slimstat-db.php:1330 msgid "Direct Pageviews" msgstr "" -#: admin/view/wp-slimstat-db.php:1081 +#: admin/view/wp-slimstat-db.php:1332 msgid "Visitors who typed your website URL directly into their browser address bar. It can also refer to visitors who clicked on one of their bookmarked links, untagged links within emails, or links in documents that don't include tracking variables." msgstr "" -#: admin/view/wp-slimstat-db.php:1083 +#: admin/view/wp-slimstat-db.php:1334 msgid "From External SERP" msgstr "" -#: admin/view/wp-slimstat-db.php:1085 +#: admin/view/wp-slimstat-db.php:1336 msgid "Visitors who clicked on a link to your website listed on a search engine result page (SERP). This metric only counts visits coming from EXTERNAL search pages." msgstr "" -#: admin/view/wp-slimstat-db.php:1087 +#: admin/view/wp-slimstat-db.php:1338 msgid "Unique Landing Pages" msgstr "" -#: admin/view/wp-slimstat-db.php:1089 +#: admin/view/wp-slimstat-db.php:1340 msgid "A landing page is the first page on your website that a visitors opens, also known as <em>entrance page</em>. For example, if they search for 'Brooklyn Office Space,' and they land on a page on your website, this page gets counted (for that visit) as a landing page." msgstr "" -#: admin/view/wp-slimstat-db.php:1091 +#: admin/view/wp-slimstat-db.php:1342 msgid "Bounce Pages" msgstr "" -#: admin/view/wp-slimstat-db.php:1093 +#: admin/view/wp-slimstat-db.php:1344 msgid "Number of single-page visits tracked over the selected period of time." msgstr "" -#: admin/view/wp-slimstat-db.php:1095 +#: admin/view/wp-slimstat-db.php:1346 msgid "New Visitors Rate" msgstr "" -#: admin/view/wp-slimstat-db.php:1097 +#: admin/view/wp-slimstat-db.php:1348 msgid "Percentage of single-page visits, i.e. visits in which the person left your site from the entrance page." msgstr "" -#: admin/view/wp-slimstat-db.php:1099 +#: admin/view/wp-slimstat-db.php:1350 msgid "Currently from search engines" msgstr "" -#: admin/view/wp-slimstat-db.php:1101 +#: admin/view/wp-slimstat-db.php:1352 msgid "Visitors who clicked on a link to your website listed on a search engine result page (SERP), tracked in the last 5 minutes." msgstr "" -#: admin/view/wp-slimstat-db.php:1113 +#: admin/view/wp-slimstat-db.php:1364 msgid "0 - 30 seconds" msgstr "" -#: admin/view/wp-slimstat-db.php:1119 +#: admin/view/wp-slimstat-db.php:1370 msgid "31 - 60 seconds" msgstr "" -#: admin/view/wp-slimstat-db.php:1125 +#: admin/view/wp-slimstat-db.php:1376 msgid "1 - 3 minutes" msgstr "" -#: admin/view/wp-slimstat-db.php:1131 +#: admin/view/wp-slimstat-db.php:1382 msgid "3 - 5 minutes" msgstr "" -#: admin/view/wp-slimstat-db.php:1137 +#: admin/view/wp-slimstat-db.php:1388 msgid "5 - 7 minutes" msgstr "" -#: admin/view/wp-slimstat-db.php:1143 +#: admin/view/wp-slimstat-db.php:1394 msgid "7 - 10 minutes" msgstr "" -#: admin/view/wp-slimstat-db.php:1149 +#: admin/view/wp-slimstat-db.php:1400 msgid "More than 10 minutes" msgstr "" -#: admin/view/wp-slimstat-db.php:1160 +#: admin/view/wp-slimstat-db.php:1411 msgid "Average Visit Duration" msgstr "" -#: admin/view/wp-slimstat-db.php:1183 -#: admin/view/wp-slimstat-reports.php:298 +#: admin/view/wp-slimstat-db.php:1434 +#: admin/view/wp-slimstat-reports.php:300 msgid "Visits" msgstr "" -#: admin/view/wp-slimstat-db.php:1185 +#: admin/view/wp-slimstat-db.php:1436 msgid "A visit is a group of pageviews within a 30-minute time span. Returning visitors are counted multiple times if they start a new visit." msgstr "" -#: admin/view/wp-slimstat-db.php:1189 +#: admin/view/wp-slimstat-db.php:1440 msgid "It includes only traffic generated by human visitors." msgstr "" -#: admin/view/wp-slimstat-db.php:1191 +#: admin/view/wp-slimstat-db.php:1442 msgid "Bounce rate" msgstr "" -#: admin/view/wp-slimstat-db.php:1193 +#: admin/view/wp-slimstat-db.php:1444 msgid "Total number of one-page visits divided by the total number of entries to a website. Please see the <a href=\"https://support.google.com/analytics/answer/1009409?hl=en\" target=\"_blank\">official Google docs</a> for more information." msgstr "" -#: admin/view/wp-slimstat-db.php:1195 +#: admin/view/wp-slimstat-db.php:1446 msgid "Known visitors" msgstr "" -#: admin/view/wp-slimstat-db.php:1197 +#: admin/view/wp-slimstat-db.php:1448 msgid "Visitors who have previously left a comment on your blog." msgstr "" -#: admin/view/wp-slimstat-db.php:1199 +#: admin/view/wp-slimstat-db.php:1450 msgid "Single-page Visits" msgstr "" -#: admin/view/wp-slimstat-db.php:1201 +#: admin/view/wp-slimstat-db.php:1452 msgid "Human users that generated one single page view on your website." msgstr "" -#: admin/view/wp-slimstat-db.php:1206 +#: admin/view/wp-slimstat-db.php:1457 msgid "Pageviews per visit" msgstr "" -#: admin/view/wp-slimstat-db.php:1209 +#: admin/view/wp-slimstat-db.php:1460 msgid "Longest visit" msgstr "" -#: admin/view/wp-slimstat-db.php:1210 +#: admin/view/wp-slimstat-db.php:1461 msgid "hits" msgstr "" -#: admin/view/wp-slimstat-db.php:1220 +#: admin/view/wp-slimstat-db.php:1474 msgid "Content Items" msgstr "" -#: admin/view/wp-slimstat-db.php:1222 +#: admin/view/wp-slimstat-db.php:1476 msgid "This value includes not only posts and pages, but any custom post type, regardless of their status." msgstr "" -#: admin/view/wp-slimstat-db.php:1224 +#: admin/view/wp-slimstat-db.php:1478 msgid "Posts" msgstr "" -#: admin/view/wp-slimstat-db.php:1227 +#: admin/view/wp-slimstat-db.php:1481 msgid "Pages" msgstr "" -#: admin/view/wp-slimstat-db.php:1230 +#: admin/view/wp-slimstat-db.php:1484 msgid "Attachments" msgstr "" -#: admin/view/wp-slimstat-db.php:1233 +#: admin/view/wp-slimstat-db.php:1487 msgid "Revisions" msgstr "" -#: admin/view/wp-slimstat-db.php:1236 +#: admin/view/wp-slimstat-db.php:1490 msgid "Comments" msgstr "" -#: admin/view/wp-slimstat-db.php:1239 +#: admin/view/wp-slimstat-db.php:1493 msgid "Avg Comments per Post" msgstr "" -#: admin/view/wp-slimstat-db.php:1242 +#: admin/view/wp-slimstat-db.php:1496 msgid "Avg Server Latency" msgstr "" -#: admin/view/wp-slimstat-db.php:1244 +#: admin/view/wp-slimstat-db.php:1498 msgid "Latency is the amount of time it takes for the host server to receive and process a request for a page object. The amount of latency depends largely on how far away the user is from the server." msgstr "" @@ -2096,7 +2404,7 @@ msgstr "" #: admin/view/wp-slimstat-reports.php:55 -#: admin/view/wp-slimstat-reports.php:288 +#: admin/view/wp-slimstat-reports.php:290 msgid "Human Visits" msgstr "" @@ -2109,7 +2417,7 @@ msgstr "" #: admin/view/wp-slimstat-reports.php:64 -#: admin/view/wp-slimstat-reports.php:846 +#: admin/view/wp-slimstat-reports.php:851 msgid "Pages with Outbound Links" msgstr "" @@ -2138,8 +2446,8 @@ msgstr "" #: admin/view/wp-slimstat-reports.php:91 -#: admin/view/wp-slimstat-reports.php:502 -#: admin/view/wp-slimstat-reports.php:511 +#: admin/view/wp-slimstat-reports.php:504 +#: admin/view/wp-slimstat-reports.php:513 msgid "Users" msgstr "" @@ -2179,6 +2487,10 @@ msgid "Bot or Crawler" msgstr "" +#: admin/view/wp-slimstat-reports.php:125 +msgid "Unique Visitors" +msgstr "" + #: admin/view/wp-slimstat-reports.php:133 msgid "At a Glance" msgstr "" @@ -2207,331 +2519,334 @@ msgid "Top Referring Domains" msgstr "" -#: admin/view/wp-slimstat-reports.php:195 +#: admin/view/wp-slimstat-reports.php:196 msgid "Top Known Visitors" msgstr "" -#: admin/view/wp-slimstat-reports.php:206 +#: admin/view/wp-slimstat-reports.php:207 msgid "Top Search Terms" msgstr "" -#: admin/view/wp-slimstat-reports.php:218 -#: admin/view/wp-slimstat-reports.php:1718 +#: admin/view/wp-slimstat-reports.php:219 +#: admin/view/wp-slimstat-reports.php:1739 msgid "Top Countries" msgstr "" -#: admin/view/wp-slimstat-reports.php:228 +#: admin/view/wp-slimstat-reports.php:229 msgid "You can configure Slimstat to not track specific Countries by setting the corresponding filter in Slimstat > Settings > Exclusions." msgstr "" -#: admin/view/wp-slimstat-reports.php:231 +#: admin/view/wp-slimstat-reports.php:232 msgid "Rankings" msgstr "" -#: admin/view/wp-slimstat-reports.php:238 +#: admin/view/wp-slimstat-reports.php:239 msgid "Slimstat retrieves live information from Alexa, Facebook and Mozscape, to measures your site's rankings. Values are updated every 12 hours. Please enter your personal access ID in the settings to access your personalized Mozscape data." msgstr "" -#: admin/view/wp-slimstat-reports.php:241 +#: admin/view/wp-slimstat-reports.php:242 msgid "Top Language Families" msgstr "" -#: admin/view/wp-slimstat-reports.php:254 +#: admin/view/wp-slimstat-reports.php:255 msgid "Users Currently Online" msgstr "" -#: admin/view/wp-slimstat-reports.php:265 +#: admin/view/wp-slimstat-reports.php:267 msgid "When visitors leave a comment on your blog, WordPress assigns them a cookie. Slimstat leverages this information to identify returning visitors. Please note that visitors also include registered users." msgstr "" -#: admin/view/wp-slimstat-reports.php:279 +#: admin/view/wp-slimstat-reports.php:281 +#: views/modules/chart-view.php:93 msgid "Unique Terms" msgstr "" -#: admin/view/wp-slimstat-reports.php:307 +#: admin/view/wp-slimstat-reports.php:309 msgid "Audience Overview" msgstr "" -#: admin/view/wp-slimstat-reports.php:314 +#: admin/view/wp-slimstat-reports.php:316 msgid "Where not otherwise specified, the metrics in this report are referred to human visitors." msgstr "" -#: admin/view/wp-slimstat-reports.php:317 +#: admin/view/wp-slimstat-reports.php:319 msgid "Top Languages" msgstr "" -#: admin/view/wp-slimstat-reports.php:328 +#: admin/view/wp-slimstat-reports.php:330 msgid "Top User Agents" msgstr "" -#: admin/view/wp-slimstat-reports.php:337 +#: admin/view/wp-slimstat-reports.php:339 msgid "This report includes all types of clients, both bots and humans." msgstr "" -#: admin/view/wp-slimstat-reports.php:340 +#: admin/view/wp-slimstat-reports.php:342 msgid "Top Service Providers" msgstr "" -#: admin/view/wp-slimstat-reports.php:349 -#: admin/view/wp-slimstat-reports.php:361 +#: admin/view/wp-slimstat-reports.php:351 +#: admin/view/wp-slimstat-reports.php:363 msgid "Internet Service Provider: a company which provides other companies or individuals with access to the Internet. Your DSL or cable internet service is provided to you by your ISP.<br><br>You can ignore specific IP addresses by setting the corresponding filter under Settings > Slimstat > Filters." msgstr "" -#: admin/view/wp-slimstat-reports.php:352 +#: admin/view/wp-slimstat-reports.php:354 msgid "Top Operating Systems" msgstr "" -#: admin/view/wp-slimstat-reports.php:364 +#: admin/view/wp-slimstat-reports.php:366 msgid "Top Screen Resolutions" msgstr "" -#: admin/view/wp-slimstat-reports.php:376 +#: admin/view/wp-slimstat-reports.php:378 msgid "Top Viewport Sizes" msgstr "" -#: admin/view/wp-slimstat-reports.php:394 +#: admin/view/wp-slimstat-reports.php:396 msgid "All values represent the percentages of pageviews within the corresponding time range." msgstr "" -#: admin/view/wp-slimstat-reports.php:397 +#: admin/view/wp-slimstat-reports.php:399 msgid "Recent Countries" msgstr "" -#: admin/view/wp-slimstat-reports.php:408 +#: admin/view/wp-slimstat-reports.php:410 msgid "Recent Viewport Sizes" msgstr "" -#: admin/view/wp-slimstat-reports.php:419 +#: admin/view/wp-slimstat-reports.php:421 msgid "Recent Operating Systems" msgstr "" -#: admin/view/wp-slimstat-reports.php:430 +#: admin/view/wp-slimstat-reports.php:432 msgid "Recent Browsers" msgstr "" -#: admin/view/wp-slimstat-reports.php:441 +#: admin/view/wp-slimstat-reports.php:443 msgid "Recent Languages" msgstr "" -#: admin/view/wp-slimstat-reports.php:452 +#: admin/view/wp-slimstat-reports.php:454 msgid "Top Browser Families" msgstr "" -#: admin/view/wp-slimstat-reports.php:461 +#: admin/view/wp-slimstat-reports.php:463 msgid "This report shows you what user agent families (no version considered) are popular among your visitors." msgstr "" -#: admin/view/wp-slimstat-reports.php:464 +#: admin/view/wp-slimstat-reports.php:466 msgid "Top OS Families" msgstr "" -#: admin/view/wp-slimstat-reports.php:475 +#: admin/view/wp-slimstat-reports.php:477 msgid "This report shows you what operating system families (no version considered) are popular among your visitors." msgstr "" -#: admin/view/wp-slimstat-reports.php:478 +#: admin/view/wp-slimstat-reports.php:480 msgid "Recent Users" msgstr "" -#: admin/view/wp-slimstat-reports.php:490 +#: admin/view/wp-slimstat-reports.php:492 msgid "Top Users" msgstr "" -#: admin/view/wp-slimstat-reports.php:512 +#: admin/view/wp-slimstat-reports.php:514 msgid "Unique Users" msgstr "" -#: admin/view/wp-slimstat-reports.php:520 +#: admin/view/wp-slimstat-reports.php:522 msgid "Top Bots" msgstr "" -#: admin/view/wp-slimstat-reports.php:532 +#: admin/view/wp-slimstat-reports.php:534 msgid "Top Human Browsers" msgstr "" -#: admin/view/wp-slimstat-reports.php:555 +#: admin/view/wp-slimstat-reports.php:557 msgid "Domains" msgstr "" -#: admin/view/wp-slimstat-reports.php:564 +#: admin/view/wp-slimstat-reports.php:566 msgid "Traffic Summary" msgstr "" -#: admin/view/wp-slimstat-reports.php:574 +#: admin/view/wp-slimstat-reports.php:576 msgid "Recent Outbound Links" msgstr "" -#: admin/view/wp-slimstat-reports.php:586 +#: admin/view/wp-slimstat-reports.php:588 msgid "Recent Posts" msgstr "" -#: admin/view/wp-slimstat-reports.php:599 +#: admin/view/wp-slimstat-reports.php:601 msgid "Recent Feeds" msgstr "" -#: admin/view/wp-slimstat-reports.php:611 +#: admin/view/wp-slimstat-reports.php:614 msgid "Recent Pages Not Found" msgstr "" -#: admin/view/wp-slimstat-reports.php:623 +#: admin/view/wp-slimstat-reports.php:626 msgid "Recent Internal Searches" msgstr "" -#: admin/view/wp-slimstat-reports.php:633 +#: admin/view/wp-slimstat-reports.php:637 msgid "Searches performed using WordPress' built-in search functionality." msgstr "" -#: admin/view/wp-slimstat-reports.php:636 +#: admin/view/wp-slimstat-reports.php:640 msgid "Top Categories" msgstr "" -#: admin/view/wp-slimstat-reports.php:648 +#: admin/view/wp-slimstat-reports.php:652 msgid "Top Downloads" msgstr "" -#: admin/view/wp-slimstat-reports.php:659 +#: admin/view/wp-slimstat-reports.php:663 msgid "You can configure Slimstat to track specific file extensions as downloads." msgstr "" -#: admin/view/wp-slimstat-reports.php:662 +#: admin/view/wp-slimstat-reports.php:666 msgid "Recent Custom Events" msgstr "" -#: admin/view/wp-slimstat-reports.php:671 -#: admin/view/wp-slimstat-reports.php:695 +#: admin/view/wp-slimstat-reports.php:675 +#: admin/view/wp-slimstat-reports.php:699 msgid "This report lists any <em>event</em> occurred on your website. Please refer to the FAQ for more information on how to use this functionality." msgstr "" -#: admin/view/wp-slimstat-reports.php:674 +#: admin/view/wp-slimstat-reports.php:678 msgid "Top Posts" msgstr "" -#: admin/view/wp-slimstat-reports.php:686 +#: admin/view/wp-slimstat-reports.php:690 msgid "Top Custom Events" msgstr "" -#: admin/view/wp-slimstat-reports.php:698 +#: admin/view/wp-slimstat-reports.php:702 msgid "Top Internal Searches" msgstr "" -#: admin/view/wp-slimstat-reports.php:710 +#: admin/view/wp-slimstat-reports.php:715 msgid "Recent Categories" msgstr "" -#: admin/view/wp-slimstat-reports.php:723 +#: admin/view/wp-slimstat-reports.php:728 msgid "Recent Tags" msgstr "" -#: admin/view/wp-slimstat-reports.php:736 +#: admin/view/wp-slimstat-reports.php:741 msgid "Top Pages Not Found" msgstr "" -#: admin/view/wp-slimstat-reports.php:748 +#: admin/view/wp-slimstat-reports.php:753 msgid "Top Authors" msgstr "" -#: admin/view/wp-slimstat-reports.php:759 +#: admin/view/wp-slimstat-reports.php:764 msgid "Top Tags" msgstr "" -#: admin/view/wp-slimstat-reports.php:771 +#: admin/view/wp-slimstat-reports.php:776 msgid "Recent Downloads" msgstr "" -#: admin/view/wp-slimstat-reports.php:783 +#: admin/view/wp-slimstat-reports.php:788 msgid "Top Outbound Links" msgstr "" -#: admin/view/wp-slimstat-reports.php:795 +#: admin/view/wp-slimstat-reports.php:800 msgid "Your Website" msgstr "" -#: admin/view/wp-slimstat-reports.php:802 +#: admin/view/wp-slimstat-reports.php:807 msgid "Your content at a glance: posts, comments, pingbacks, etc. Please note that this report is not affected by the filters set here above." msgstr "" -#: admin/view/wp-slimstat-reports.php:805 +#: admin/view/wp-slimstat-reports.php:810 msgid "Top Bounce Pages" msgstr "" -#: admin/view/wp-slimstat-reports.php:819 +#: admin/view/wp-slimstat-reports.php:824 msgid "Top Exit Pages" msgstr "" -#: admin/view/wp-slimstat-reports.php:832 +#: admin/view/wp-slimstat-reports.php:837 msgid "Top Entry Pages" msgstr "" -#: admin/view/wp-slimstat-reports.php:855 +#: admin/view/wp-slimstat-reports.php:860 msgid "Outbound Links" msgstr "" -#: admin/view/wp-slimstat-reports.php:856 +#: admin/view/wp-slimstat-reports.php:861 msgid "Unique Outbound" msgstr "" -#: admin/view/wp-slimstat-reports.php:864 +#: admin/view/wp-slimstat-reports.php:869 msgid "Users by Page" msgstr "" -#: admin/view/wp-slimstat-reports.php:875 +#: admin/view/wp-slimstat-reports.php:880 msgid "Audience Location" msgstr "" -#: admin/view/wp-slimstat-reports.php:882 +#: admin/view/wp-slimstat-reports.php:887 msgid "Dots on the map represent the most recent pageviews geolocated by City. This feature is only available by enabling the corresponding precision level in the settings." msgstr "" -#: admin/view/wp-slimstat-reports.php:888 +#: admin/view/wp-slimstat-reports.php:893 msgid "Top Cities" msgstr "" -#: admin/view/wp-slimstat-reports.php:958 +#: admin/view/wp-slimstat-reports.php:992 +#: src/Reports/Abstracts/AbstractReport.php:275 msgid "Refresh" msgstr "" -#: admin/view/wp-slimstat-reports.php:1015 +#: admin/view/wp-slimstat-reports.php:1055 #, php-format msgid "Showing %s - %s of %s" msgstr "" -#: admin/view/wp-slimstat-reports.php:1018 +#: admin/view/wp-slimstat-reports.php:1058 msgid "Refresh in" msgstr "" -#: admin/view/wp-slimstat-reports.php:1145 +#: admin/view/wp-slimstat-reports.php:1207 msgid "Tag" msgstr "" -#: admin/view/wp-slimstat-reports.php:1148 -#: admin/view/wp-slimstat-reports.php:1157 +#: admin/view/wp-slimstat-reports.php:1210 +#: admin/view/wp-slimstat-reports.php:1219 msgid "ID" msgstr "" -#: admin/view/wp-slimstat-reports.php:1154 +#: admin/view/wp-slimstat-reports.php:1216 msgid "Category" msgstr "" -#: admin/view/wp-slimstat-reports.php:1173 -#: admin/view/wp-slimstat-reports.php:1198 -#: admin/view/wp-slimstat-reports.php:1204 +#: admin/view/wp-slimstat-reports.php:1235 +#: admin/view/wp-slimstat-reports.php:1263 +#: admin/view/wp-slimstat-reports.php:1268 msgid "Code" msgstr "" -#: admin/view/wp-slimstat-reports.php:1247 +#: admin/view/wp-slimstat-reports.php:1311 msgid "URL" msgstr "" -#: admin/view/wp-slimstat-reports.php:1267 +#: admin/view/wp-slimstat-reports.php:1331 msgid "No referrer" msgstr "" -#: admin/view/wp-slimstat-reports.php:1270 +#: admin/view/wp-slimstat-reports.php:1334 +#: src/Services/Privacy/DataExporter.php:68 msgid "Referrer" msgstr "" -#: admin/view/wp-slimstat-reports.php:1287 -#: admin/view/wp-slimstat-reports.php:1313 +#: admin/view/wp-slimstat-reports.php:1351 +#: admin/view/wp-slimstat-reports.php:1377 #: languages/index.php:11 #: languages/index.php:14 #: languages/index.php:15 @@ -2544,85 +2859,65 @@ msgid "Unknown" msgstr "" -#: admin/view/wp-slimstat-reports.php:1291 -#: admin/view/wp-slimstat-reports.php:1317 +#: admin/view/wp-slimstat-reports.php:1355 +#: admin/view/wp-slimstat-reports.php:1381 msgid "Guest" msgstr "" -#: admin/view/wp-slimstat-reports.php:1466 +#: admin/view/wp-slimstat-reports.php:1535 msgid "Page" msgstr "" -#: admin/view/wp-slimstat-reports.php:1466 +#: admin/view/wp-slimstat-reports.php:1535 msgid "Date" msgstr "" -#: admin/view/wp-slimstat-reports.php:1526 -#: admin/view/wp-slimstat-reports.php:1528 +#: admin/view/wp-slimstat-reports.php:1595 +#: admin/view/wp-slimstat-reports.php:1597 msgid "Filter by element in a group" msgstr "" -#: admin/view/wp-slimstat-reports.php:1564 +#: admin/view/wp-slimstat-reports.php:1634 msgid "Moz Domain Authority" msgstr "" -#: admin/view/wp-slimstat-reports.php:1565 +#: admin/view/wp-slimstat-reports.php:1635 msgid "A normalized 100-point score representing the likelihood of a domain to rank well in search engine results." msgstr "" -#: admin/view/wp-slimstat-reports.php:1569 +#: admin/view/wp-slimstat-reports.php:1639 msgid "Moz Backlinks" msgstr "" -#: admin/view/wp-slimstat-reports.php:1570 +#: admin/view/wp-slimstat-reports.php:1640 msgid "Number of external equity links to your website." msgstr "" -#: admin/view/wp-slimstat-reports.php:1574 +#: admin/view/wp-slimstat-reports.php:1644 msgid "Moz Links" msgstr "" -#: admin/view/wp-slimstat-reports.php:1575 +#: admin/view/wp-slimstat-reports.php:1645 msgid "The number of links (external, equity or nonequity or not) to your homepage." msgstr "" -#: admin/view/wp-slimstat-reports.php:1579 -msgid "Alexa World Rank" -msgstr "" - -#: admin/view/wp-slimstat-reports.php:1580 -msgid "Alexa is a subsidiary company of Amazon.com which provides commercial web traffic data." -msgstr "" - -#: admin/view/wp-slimstat-reports.php:1584 -msgid "Alexa Country Rank" -msgstr "" - -#: admin/view/wp-slimstat-reports.php:1589 -msgid "Alexa Popularity" -msgstr "" - -#: admin/view/wp-slimstat-reports.php:1648 -msgid "Alexa Delta" -msgstr "" - -#: admin/view/wp-slimstat-reports.php:1800 +#: admin/view/wp-slimstat-reports.php:1833 msgid "src" msgstr "" -#: admin/view/wp-slimstat-reports.php:1804 +#: admin/view/wp-slimstat-reports.php:1837 msgid "serp" msgstr "" -#: admin/view/wp-slimstat-reports.php:1831 +#: admin/view/wp-slimstat-reports.php:1864 msgid "Remove filter for" msgstr "" -#: admin/view/wp-slimstat-reports.php:1836 +#: admin/view/wp-slimstat-reports.php:1869 msgid "Save" msgstr "" -#: admin/view/wp-slimstat-reports.php:1840 +#: admin/view/wp-slimstat-reports.php:1873 msgid "Reset All" msgstr "" @@ -4786,61 +5081,371 @@ msgid "GeoIP database file is missing or corrupt. Please go to Settings -> Tracker and click on the \"Update Database\" button to download a fresh copy." msgstr "" -#: src/Components/View.php:38 +#: src/Components/DateRangeHelper.php:201 +msgid "This week" +msgstr "" + +#: src/Components/DateRangeHelper.php:202 +msgid "Last week" +msgstr "" + +#: src/Components/DateRangeHelper.php:203 +msgid "This Month" +msgstr "" + +#: src/Components/DateRangeHelper.php:204 +msgid "Previous Month" +msgstr "" + +#: src/Components/DateRangeHelper.php:205 +msgid "Last 7 Days" +msgstr "" + +#: src/Components/DateRangeHelper.php:206 +msgid "Last 28 Days" +msgstr "" + +#: src/Components/DateRangeHelper.php:207 +msgid "Last 30 Days" +msgstr "" + +#: src/Components/DateRangeHelper.php:208 +msgid "Last 90 Days" +msgstr "" + +#: src/Components/DateRangeHelper.php:209 +msgid "Last 6 Months" +msgstr "" + +#: src/Components/DateRangeHelper.php:210 +msgid "This Year" +msgstr "" + +#: src/Components/DateRangeHelper.php:211 +msgid "Custom Range" +msgstr "" + +#: src/Components/DateRangeHelper.php:213 +msgid "Cancel" +msgstr "" + +#: src/Components/DateRangeHelper.php:214 +msgid "Clear Cache" +msgstr "" + +#: src/Components/DateRangeHelper.php:215 +msgid "Clearing..." +msgstr "" + +#: src/Components/DateRangeHelper.php:216 +msgid "Cleared!" +msgstr "" + +#: src/Components/DateRangeHelper.php:217 +msgid "Error" +msgstr "" + +#: src/Components/DateRangeHelper.php:232 +msgid "Invalid date format" +msgstr "" + +#: src/Components/DateRangeHelper.php:239 +msgid "Start date must be before end date" +msgstr "" + +#: src/Components/DateRangeHelper.php:248 +msgid "Date range too long. Maximum 3 years allowed." +msgstr "" + +#: src/Components/DateRangeHelper.php:256 +msgid "Start date cannot be in the future" +msgstr "" + +#: src/Components/DateRangeHelper.php:263 +msgid "End date cannot be in the future" +msgstr "" + +#: src/Components/View.php:89 msgid "View file not found: " msgstr "" +#: src/Controllers/Rest/ConsentChangeRestController.php:153 +#: src/Controllers/Rest/GDPRBannerRestController.php:74 +#: src/Services/Privacy/ConsentHandler.php:101 +msgid "Invalid security token." +msgstr "" + +#: src/Controllers/Rest/ConsentChangeRestController.php:165 +msgid "Invalid consent data format." +msgstr "" + +#: src/Controllers/Rest/GDPRBannerRestController.php:84 +#: src/Services/Privacy/ConsentHandler.php:112 +msgid "SlimStat banner is not enabled." +msgstr "" + +#: src/Controllers/Rest/GDPRBannerRestController.php:98 +#: src/Services/Privacy/ConsentHandler.php:135 +msgid "Failed to set consent cookie." +msgstr "" + +#: src/Controllers/Rest/GDPRBannerRestController.php:110 +#: src/Services/Privacy/ConsentHandler.php:221 +msgid "Consent granted." +msgstr "" + +#: src/Controllers/Rest/GDPRBannerRestController.php:111 +#: src/Services/Privacy/ConsentHandler.php:222 +msgid "Consent denied." +msgstr "" + +#: src/Controllers/Rest/TrackingRestController.php:179 +msgid "[REST API] Tracking failed, falling back to alternative methods." +msgstr "" + +#: src/Decorators/NotificationDecorator.php:152 +msgid "ago" +msgstr "" + #: src/Exception/LogException.php:20 #, php-format msgid "Exception occurred: [Code %d] %s at %s:%d" msgstr "" -#: src/Modules/Chart.php:47 +#. translators: %1$s is the index name, %2$s is the table name. +#: src/Migration/AbstractIndexMigration.php:23 +#, php-format +msgid "Ensures the %1$s index exists on the %2$s table for performance." +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:41 +#: src/Migration/Admin/MigrationAdmin.php:42 +msgid "Migration" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:90 +msgid "Running migrations…" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:91 +msgid "In progress…" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:92 +msgid "Done" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:93 +msgid "Failed" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:94 +msgid "All migrations finished." +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:196 +#: src/view/migration-page.php:11 +msgid "SlimStat Database Migration" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:197 +msgid "SlimStat database migration required" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:198 +msgid "To improve SlimStat performance and stability, your database needs to be migrated." +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:202 +msgid "Technical details" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:218 +msgid "Go to Migration Page" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:265 +msgid "All migrations completed successfully!" +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:265 +msgid "Migrations completed, but some may still be needed." +msgstr "" + +#: src/Migration/Admin/MigrationAdmin.php:304 +msgid "Migration dismissal reset" +msgstr "" + +#: src/Migration/Migrations/CreateCountryDtIndex.php:17 +msgid "Create country, dt Index" +msgstr "" + +#: src/Migration/Migrations/CreateDtBrowserIndex.php:17 +msgid "Create Browser Index" +msgstr "" + +#: src/Migration/Migrations/CreateDtOutIndex.php:17 +msgid "Create dt_out Index" +msgstr "" + +#: src/Migration/Migrations/CreateDtPlatformIndex.php:17 +msgid "Create Platform Index" +msgstr "" + +#: src/Migration/Migrations/CreateDtScreenIndex.php:17 +msgid "Create Screen Resolution Index" +msgstr "" + +#: src/Modules/Chart.php:50 +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:584 msgid "Insufficient permissions" msgstr "" -#: src/Modules/Chart.php:54 +#: src/Modules/Chart.php:57 msgid "Invalid granularity" msgstr "" -#: src/Modules/Chart.php:113 +#: src/Modules/Chart.php:126 msgid "-- Previous Period" msgstr "" -#: src/Modules/Chart.php:114 +#: src/Modules/Chart.php:127 msgid "Click Tap “Previous Period” to hide or show the previous period line." msgstr "" -#: src/Modules/Chart.php:116 +#: src/Modules/Chart.php:129 msgid "30 Days ago" msgstr "" -#: src/Modules/Chart.php:117 +#: src/Modules/Chart.php:130 msgid "Day ago" msgstr "" -#: src/Modules/Chart.php:118 +#: src/Modules/Chart.php:131 msgid "Year ago" msgstr "" -#: src/Modules/Chart.php:119 -msgid "Now" -msgstr "" - -#: src/Modules/Chart.php:383 -#: src/Modules/Chart.php:400 +#: src/Modules/Chart.php:478 +#: src/Modules/Chart.php:495 msgid "Invalid SQL function in chart data expression" msgstr "" -#: src/Modules/Chart.php:387 -#: src/Modules/Chart.php:404 +#: src/Modules/Chart.php:482 +#: src/Modules/Chart.php:499 msgid "Invalid column name in chart data expression" msgstr "" -#: src/Modules/Chart.php:412 +#: src/Modules/Chart.php:507 msgid "Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed." msgstr "" +#: src/Reports/Abstracts/AbstractReport.php:355 +msgid "No content available for this report." +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:39 +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:43 +msgid "Live Analytics" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:44 +msgid "Real-time analytics with second-level accuracy showing current user activity and trends." +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:46 +msgid "• Users Live: Unique sessions active within the last 30 minutes" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:47 +msgid "• Counters use the latest dt/dt_out data so long reads remain “online” until they go idle" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:48 +msgid "• Deferred dt_out updates backfill past minutes so long reads remain visible on the chart" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:49 +msgid "• Chart shows exact user count for each minute of the last 30 minutes" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:50 +msgid "• Pages Live: Unique pages viewed in the last 30 minutes" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:51 +msgid "• Countries Live: Number of countries with active users in the last 30 minutes" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:52 +msgid "• Data refreshes every 10 seconds with a short-lived cache for stability" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:53 +msgid "• Red bars highlight peak activity periods" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:565 +msgid "Too many requests. Please try again later." +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:574 +msgid "Security check failed" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:602 +msgid "Invalid report ID format" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:620 +msgid "An error occurred while fetching data" +msgstr "" + +#: src/Reports/Types/Analytics/LiveAnalyticsReport.php:627 +msgid "A fatal error occurred while fetching data" +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:23 +#: src/Services/Admin/Notification/NotificationActions.php:47 +#: src/Services/Admin/Notification/NotificationActions.php:69 +msgid "Permission denied." +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:31 +msgid "All notifications have been dismissed." +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:34 +msgid "Notification has been dismissed." +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:54 +msgid "Notifications status has been updated." +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:56 +msgid "Notifications status has not been updated." +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:97 +msgid "Notifications refreshed." +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:118 +#: views/components/notification/side-bar.php:76 +msgid "dismissed list" +msgstr "" + +#: src/Services/Admin/Notification/NotificationActions.php:118 +#: views/components/notification/no-data.php:2 +#: views/components/notification/side-bar.php:59 +msgid "inbox" +msgstr "" + +#: src/Services/Admin/Notification/NotificationFetcher.php:43 +#, php-format +msgid "No notifications were found. The API returned an empty response from the following URL: %s" +msgstr "" + #: src/Services/Browscap.php:26 msgid "The Browscap Cache folder could not be opened on your filesystem. Please check your server permissions and try again." msgstr "" @@ -4877,76 +5482,326 @@ msgid "The Browscap data file has been installed on your server." msgstr "" -#: src/Services/GeoIP.php:153 -msgid "GeoIP Database Already Exists!" +#: src/Services/GDPRService.php:165 +msgid "This website uses cookies to analyze site traffic and improve your experience. By continuing to use this site, you consent to our use of cookies." +msgstr "" + +#: src/Services/GDPRService.php:184 +#: wp-slimstat.php:820 +msgid "Accept" +msgstr "" + +#: src/Services/GDPRService.php:187 +msgid "Deny" +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:25 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:80 +msgid "Invalid MaxMind license key format. License key should be 16-40 characters containing only letters, numbers, and underscores." msgstr "" -#: src/Services/GeoIP.php:161 -msgid "Error: <code>gzopen()</code> Function Not Found!" +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:104 +msgid "Failed to create temporary file for MaxMind database download." msgstr "" -#: src/Services/GeoIP.php:178 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:146 #, php-format -msgid "Error Creating GeoIP Database Directory. Ensure Web Server Has Directory Creation Permissions in: %s" +msgid "Network error downloading MaxMind database: %s" +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:158 +msgid "Failed to stage downloaded MaxMind archive." +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:172 +msgid "MaxMind update requires the PHP Phar extension (PharData class not found). Please enable Phar extension or upload the .mmdb file manually to wp-content/uploads/wp-slimstat/." msgstr "" -#: src/Services/GeoIP.php:182 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:191 +msgid "Failed to create temporary extraction directory." +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:227 +msgid "Source file is not readable" +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:230 #, php-format -msgid "Error Setting Permissions for GeoIP Database Directory. Check Write Permissions for Directories in: %s" +msgid "Destination directory is not writable: %s" msgstr "" -#: src/Services/GeoIP.php:190 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:233 +msgid "Destination file exists but is not writable" +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:237 #, php-format -msgid "Error Downloading GeoIP Database from: %1$s - %2$s" +msgid ".mmdb file was found but could not be moved or copied to destination. Source: %s, Destination: %s" msgstr "" -#: src/Services/GeoIP.php:211 -msgid "There was an error creating the GeoIP database file." +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:243 +#, php-format +msgid "Diagnostic info: %s" msgstr "" -#: src/Services/GeoIP.php:223 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:264 #, php-format -msgid "Error Opening Downloaded GeoIP Database for Reading: %s" +msgid "No .mmdb file found in MaxMind database archive. Files found: %s" msgstr "" -#: src/Services/GeoIP.php:227 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:283 #, php-format -msgid "Error Opening Destination GeoIP Database for Writing: %s" +msgid "Error extracting MaxMind database: %s" msgstr "" -#: src/Services/GeoIP.php:244 -msgid "GeoIP Database Successfully Updated!" +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:293 +#, php-format +msgid "Fatal error updating MaxMind database: %s" msgstr "" -#: src/Services/GeoIP.php:248 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:315 #, php-format -msgid "Error: %1$s" +msgid "DNS resolution failed for %s. Please check your internet connection and DNS settings." msgstr "" -#: src/Services/GeoIP.php:266 -msgid "The provided URL is invalid." +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:323 +#, php-format +msgid "Cannot connect to MaxMind servers. Network error: %s" msgstr "" -#: src/Services/GeoIP.php:273 -msgid "A temporary file could not be created. Please check your server's file permissions and try again." +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:331 +#, php-format +msgid "Network connectivity check failed: %s" msgstr "" -#: src/Services/GeoService.php:115 -msgid "GeoIP is disabled. Please first choose GeoIP Database Source and save settings!" +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:339 +#, php-format +msgid "HTTP %d error downloading MaxMind database" msgstr "" -#: src/Services/GeoService.php:137 +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:342 +msgid "Unauthorized. Please check your MaxMind license key. The key may be invalid, expired, or not authorized for GeoLite2 downloads." +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:344 +msgid "Forbidden. Your license key does not have permission to download this database, or you have exceeded the download limit." +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:346 +msgid "Database not found. The requested edition may not exist or may not be available for your account type." +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:348 +msgid "Too many requests. You have exceeded the download limit. Please wait before trying again." +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:353 +msgid "MaxMind server error. Please try again later." +msgstr "" + +#: src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php:359 +msgid "License key authentication failed. Please verify your MaxMind license key." +msgstr "" + +#: src/Services/GeoService.php:113 +msgid "GeoIP is disabled. Please choose a DB-based provider and save settings." +msgstr "" + +#: src/Services/GeoService.php:131 msgid "GeoIP database not found!" msgstr "" -#: src/Services/GeoService.php:145 +#: src/Services/GeoService.php:147 msgid "GeoIP database is working fine!" msgstr "" -#: src/Services/GeoService.php:152 +#: src/Services/GeoService.php:154 msgid "GeoIP database file is corrupt. Please click on the \"Update Database\" button to download a fresh copy." msgstr "" +#: src/Services/Privacy/ConsentHandler.php:55 +msgid "Consent revoked and cookie deleted." +msgstr "" + +#: src/Services/Privacy/ConsentHandler.php:122 +msgid "Invalid consent value." +msgstr "" + +#: src/Services/Privacy/DataEraser.php:59 +#, php-format +msgid "Removed %d pageview record(s) from SlimStat." +msgstr "" + +#: src/Services/Privacy/DataEraser.php:82 +#, php-format +msgid "Removed %d archived pageview record(s) from SlimStat." +msgstr "" + +#: src/Services/Privacy/DataEraser.php:150 +#, php-format +msgid "Removed %d event record(s) from SlimStat." +msgstr "" + +#: src/Services/Privacy/DataEraser.php:175 +#, php-format +msgid "Removed %d archived event record(s) from SlimStat." +msgstr "" + +#: src/Services/Privacy/DataEraser.php:244 +#, php-format +msgid "Anonymized %d record(s) with IP %s in SlimStat." +msgstr "" + +#: src/Services/Privacy/DataEraser.php:275 +#: src/Services/Privacy/DataExporter.php:158 +#: src/Services/Privacy/DataExporter.php:280 +msgid "SlimStat Pageviews" +msgstr "" + +#: src/Services/Privacy/DataEraser.php:280 +#: src/Services/Privacy/DataExporter.php:247 +#: src/Services/Privacy/DataExporter.php:285 +msgid "SlimStat Events" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:55 +msgid "Visit Date" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:61 +msgid "Page Visited" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:76 +msgid "Username" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:83 +msgid "Email" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:98 +msgid "Browser Fingerprint" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:213 +msgid "Event Date" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:219 +msgid "Event Type" +msgstr "" + +#: src/Services/Privacy/DataExporter.php:233 +msgid "Position" +msgstr "" + +#: src/Tracker/Processor.php:128 +#, php-format +msgid "Attempted XSS Injection: %s" +msgstr "" + +#: src/view/migration-page.php:16 +msgid "Migration Status" +msgstr "" + +#: src/view/migration-page.php:19 +msgid "Ready to start" +msgstr "" + +#: src/view/migration-page.php:19 +msgid "Migrating database…" +msgstr "" + +#: src/view/migration-page.php:19 +msgid "Migration complete" +msgstr "" + +#: src/view/migration-page.php:19 +msgid "Migration failed" +msgstr "" + +#: src/view/migration-page.php:20 +msgid "Idle" +msgstr "" + +#: src/view/migration-page.php:23 +msgid "We are migrating your database to improve SlimStat performance and stability. Keep this page open until the process finishes. You can review each step below." +msgstr "" + +#: src/view/migration-page.php:26 +msgid "Total steps" +msgstr "" + +#: src/view/migration-page.php:27 +msgid "Completed" +msgstr "" + +#: src/view/migration-page.php:28 +msgid "Remaining" +msgstr "" + +#: src/view/migration-page.php:29 +msgid "Elapsed" +msgstr "" + +#: src/view/migration-page.php:39 +msgid "Click \"Start Migration\" to begin. Progress and details will appear here." +msgstr "" + +#: src/view/migration-page.php:41 +msgid "No migrations are required. Your database is up to date." +msgstr "" + +#: src/view/migration-page.php:46 +msgid "Migration Steps & Diagnostics" +msgstr "" + +#: src/view/migration-page.php:53 +msgid "Start Migration" +msgstr "" + +#: src/view/migration-page.php:58 +#: src/view/migration-page.php:62 +msgid "Back to Dashboard" +msgstr "" + +#: views/components/notification/card.php:34 +msgid "Dismiss" +msgstr "" + +#: views/components/notification/no-data.php:15 +msgid "You are up to date!" +msgstr "" + +#. translators: %s: notification tab name +#: views/components/notification/no-data.php:20 +#, php-format +msgid "No notifications in your %s." +msgstr "" + +#: views/components/notification/side-bar.php:13 +#: views/components/notification/side-bar.php:14 +msgid "Refresh notifications" +msgstr "" + +#: views/components/notification/side-bar.php:16 +#: views/components/notification/side-bar.php:17 +msgid "Close notifications" +msgstr "" + +#: views/components/notification/side-bar.php:24 +msgid "Inbox" +msgstr "" + +#: views/components/notification/side-bar.php:26 +msgid "Dismissed" +msgstr "" + +#: views/components/notification/side-bar.php:41 +msgid "Dismiss all" +msgstr "" + #: views/modules/chart-view.php:12 msgid "Yearly" msgstr "" @@ -4967,56 +5822,215 @@ msgid "Hourly" msgstr "" -#: wp-slimstat.php:581 -#, php-format -msgid "Attempted XSS Injection: %s" +#: views/reports/live-analytics.php:67 +msgid "Users live" +msgstr "" + +#: views/reports/live-analytics.php:85 +msgid "Pages live" +msgstr "" + +#: views/reports/live-analytics.php:103 +msgid "Countries live" +msgstr "" + +#: views/reports/live-analytics.php:118 +#: views/reports/live-analytics.php:171 +msgid "LIVE" +msgstr "" + +#: views/reports/live-analytics.php:120 +#: views/reports/live-analytics.php:173 +msgid "Online users per minute" +msgstr "" + +#: views/reports/live-analytics.php:126 +msgid "We're not seeing activity in the last 30 minutes yet." +msgstr "" + +#: wp-slimstat.php:265 +msgid "Session cookie that identifies returning visitors for analytics." msgstr "" -#: wp-slimstat.php:897 +#: wp-slimstat.php:434 msgid "Invalid Report ID" msgstr "" -#: wp-slimstat.php:1064 +#: wp-slimstat.php:611 msgid "[REST API] The <code>dimension</code> parameter is required. Please review your request and try again." msgstr "" -#: wp-slimstat.php:1068 +#: wp-slimstat.php:615 msgid "[REST API] The <code>function</code> parameter is required. Please review your request and try again." msgstr "" -#: wp-slimstat.php:1099 +#: wp-slimstat.php:646 msgid "[REST API] You sent an invalid request. Accepted function values include: <code>count, count-all, recent, recent-all, top and top-all</code>. Please review your request and try again." msgstr "" -#: wp-slimstat.php:1113 +#: wp-slimstat.php:660 msgid "[REST API] Please use a valid token in order to access the REST API endpoint at this URL." msgstr "" -#: wp-slimstat.php:1130 +#: wp-slimstat.php:683 msgid "You will need to specify a valid token to be able to query the data. Tokens are defined in Slimstat > Settings > Access Control." msgstr "" -#: wp-slimstat.php:1134 +#: wp-slimstat.php:687 msgid "This parameter specifies the type of QUERY you would like to perform. Accepted funciton values include: count, count-all, recent, recent-all, top and top-all." msgstr "" -#: wp-slimstat.php:1139 +#: wp-slimstat.php:692 msgid "This parameter indicates what dimension to return: * (all data), ip, resource, browser, operating system, etc. You can only specify one dimension at a time." msgstr "" -#: wp-slimstat.php:1144 +#: wp-slimstat.php:697 #, php-format msgid "This parameter is used to filter a given dimension (resources, browsers, operating systems, etc) so that it satisfies certain conditions (i.e.: browser contains Chrome). Please make sure to urlencode this value, and to use the usual filter format: browser contains Chrome&&&referer contains slim (encoded: browser%20contains%20Chrome%26%26%26referer%20contains%20slim)" msgstr "" -#: wp-slimstat.php:2252 +#: wp-slimstat.php:821 +msgid "Decline" +msgstr "" + +#: wp-slimstat.php:1234 +msgid "What personal data we collect and why" +msgstr "" + +#: wp-slimstat.php:1235 +msgid "SlimStat Analytics collects the following data about website visitors:" +msgstr "" + +#: wp-slimstat.php:1237 +msgid "IP Address: Collected for analytics and security purposes. May be anonymized or hashed based on your privacy settings." +msgstr "" + +#: wp-slimstat.php:1238 +msgid "Page URLs: Tracks which pages are visited to analyze website usage." +msgstr "" + +#: wp-slimstat.php:1239 +msgid "Referrer Information: Tracks where visitors came from (search engines, other websites, etc.)." +msgstr "" + +#: wp-slimstat.php:1240 +msgid "Browser and Device Information: User agent, screen resolution, and device type for analytics." +msgstr "" + +#: wp-slimstat.php:1241 +msgid "Timestamp: Date and time of each page visit." +msgstr "" + +#: wp-slimstat.php:1244 +msgid "Cookies: A tracking cookie is used to identify returning visitors and maintain session continuity." +msgstr "" + +#: wp-slimstat.php:1248 +msgid "User Information: If you are logged in, your username and email may be associated with your visits (only with consent when GDPR mode is enabled)." +msgstr "" + +#: wp-slimstat.php:1253 +msgid "How long we retain your data" +msgstr "" + +#: wp-slimstat.php:1256 +#, php-format +msgid "Analytics data is automatically deleted after %d days, in compliance with GDPR data retention requirements." +msgstr "" + +#: wp-slimstat.php:1258 +msgid "Analytics data retention is currently disabled. Please contact the site administrator for information about data retention policies." +msgstr "" + +#: wp-slimstat.php:1261 +msgid "Your rights" +msgstr "" + +#: wp-slimstat.php:1262 +msgid "Under GDPR, you have the right to:" +msgstr "" + +#: wp-slimstat.php:1264 +msgid "Access your personal data collected by SlimStat" +msgstr "" + +#: wp-slimstat.php:1265 +msgid "Request deletion of your personal data (Right to be Forgotten)" +msgstr "" + +#: wp-slimstat.php:1266 +msgid "Opt-out of tracking by revoking consent (if GDPR mode is enabled)" +msgstr "" + +#: wp-slimstat.php:1270 +msgid "You can exercise these rights by using the WordPress Privacy Tools (Tools → Export Personal Data / Erase Personal Data) or by contacting the site administrator." +msgstr "" + +#: wp-slimstat.php:1275 +msgid "This website uses Anonymous Tracking Mode. Initial tracking occurs without collecting personally identifiable information (PII). Full tracking with PII collection only occurs after you grant explicit consent." +msgstr "" + +#: wp-slimstat.php:1277 +msgid "Tracking requires your consent when GDPR mode is enabled. You can grant or revoke consent at any time through the consent management interface." +msgstr "" + +#: wp-slimstat.php:1458 msgid "Report" msgstr "" -#: wp-slimstat.php:2260 +#: wp-slimstat.php:1466 msgid "Title" msgstr "" -#: wp-slimstat.php:2265 +#: wp-slimstat.php:1471 msgid "Optional filters" msgstr "" + +#: wp-slimstat.php:1561 +msgid "Invalid nonce" +msgstr "" + +#: wp-slimstat.php:1573 +#, php-format +msgid "Slimstat cache cleared (%d items)" +msgstr "" + +#: admin/assets/js/admin.js:492 +#: admin/assets/js/admin.js:930 +msgid "Select value..." +msgstr "" + +#: admin/assets/js/admin.js:493 +msgid "Search..." +msgstr "" + +#: admin/assets/js/admin.js:494 +msgid "No results found" +msgstr "" + +#: admin/assets/js/admin.js:495 +msgid "Loading..." +msgstr "" + +#: admin/assets/js/admin.js:892 +#: admin/assets/js/admin.js:933 +msgid "Loading options..." +msgstr "" + +#: admin/assets/js/admin.js:921 +msgid "No matching options found" +msgstr "" + +#: admin/assets/js/admin.js:925 +msgid "No data in this time range" +msgstr "" + +#: admin/assets/js/admin.js:931 +msgid "Search options..." +msgstr "" + +#: admin/assets/js/admin.js:943 +#: admin/assets/js/admin.js:947 +#: admin/assets/js/admin.js:952 +msgid "Enter value..." +msgstr "" @@ -4,8 +4,8 @@ Text Domain: wp-slimstat Requires at least: 5.6 Requires PHP: 7.4 -Tested up to: 6.8 -Stable tag: 5.3.5 +Tested up to: 6.9.1 +Stable tag: 5.4.0 License: GPL-2.0+ License URI: https://www.gnu.org/licenses/gpl-2.0.html @@ -18,7 +18,7 @@ * **Real-Time Access Log**: measure server latency, track page events, keep an eye on your bounce rate and much more. * **Shortcodes**: display reports in widgets or directly in posts and pages. * **Customize Reports**: Customize all pages—Real-time, Overview, Audience, Site Analysis, and Traffic Sources—to fit your needs easily! -* **GDPR**: fully compliant with the GDPR European law. You can test your website at [cookiebot.com](https://www.cookiebot.com/en/). +* **GDPR**: fully compliant with GDPR European law. Integrates seamlessly with popular Consent Management Platforms (WP Consent API, Real Cookie Banner). * **Filters**: exclude users from statistics collection based on various criteria, including user roles, common robots, IP subnets, admin pages, country, etc. * **Export to Excel**: download your reports as CSV files, generate user heatmaps or get daily emails right in your mailbox (via Pro). * **Cache**: compatible with W3 Total Cache, WP SuperCache, CloudFlare and most caching plugins. @@ -73,6 +73,25 @@ 9. **Settings** - Plenty of options to customize the plugin's behavior == Changelog == += 5.4.0 - 2026-03-08 = +- **Breaking**: Removed internal GDPR consent management system (shortcode, banner, opt-in/opt-out cookies) in favor of external CMP integrations. +- **New**: Integration with Consent Management Platforms (CMPs) for GDPR compliance: WP Consent API and Real Cookie Banner Pro. +- **New**: GDPR Compliance Mode toggle - Enable/disable GDPR compliance requirements (default: enabled). +- **New**: Consent change listener that automatically resumes tracking when user grants consent via CMP. +- **New**: Do Not Track (DNT) header respect with configurable option in settings. +- **New**: WordPress Privacy Policy content registration for GDPR Article 13/14 compliance. +- **Enhancement**: Refactored GDPR architecture - consent management fully delegated to external CMPs. +- **Enhancement**: Smart IP handling - automatically upgrades from anonymized/hashed IP to full IP when consent is granted. +- **Enhancement**: Improved JavaScript consent handling with polling-based consent state monitoring. +- **Enhancement**: Default data retention period set to 420 days (14 months) for GDPR compliance. +- **Fix**: Legacy mode now conservatively denies PII collection when GDPR enabled and no CMP configured. +- **Fix**: Consent revocation properly deletes tracking cookie when user opts out via banner. +- **Fix**: Removed legacy cookie-based opt-in/opt-out handling for cleaner, CMP-based consent flow. +[See full release notes](https://wp-slimstat.com/wordpress-analytics-plugin-slimstat-5-4-release-notes/?utm_source=wordpress&utm_medium=changelog&utm_campaign=changelog&utm_content=5-4-0) + += 5.3.6 = +* Security: Hardened output escaping in reports + = 5.3.5 - 2025-12-31 = * Security: Hardened plugin security @@ -102,3 +121,28 @@ - **Enhancement**: Compatibility with WordPress’s Interactivity API for seamless integration. - **Enhancement**: Added new 3 date ranges formats (Last 2 weeks, Previous month, This month). [See full release notes](https://wp-slimstat.com/wordpress-analytics-plugin-slimstat-5-3-release-notes/?utm_source=wordpress&utm_medium=changelog&utm_campaign=changelog&utm_content=5-3-0) + += 5.2.13 - 2025-04-29 = +- **Fix**: Resolved issues with pagination in reports. + += 5.2.12 - 2025-04-26 = +- **Enhancement**: Removed red color from report export boxes to reduce eye strain and improve user experience. + += 5.2.11 - 2025-04-25 = +- Full release notes → [WordPress Real-time Analytics Plugin](https://wp-slimstat.com/wordpress-analytics-plugin-slimstat-5-2-11-release-notes/?utm_source=wordpress&utm_medium=changelog&utm_campaign=changelog&utm_content=5-2-11) – SlimStat 5.2.11 Release Notes +- **Visual Enhancement**: Improved UI with eye-catching visual elements for better user experience. +- **Enhancement**: Optimized SQL query to reduce the chances of errors and improve overall performance. +- **Enhancement**: The "Export" button for non-Pro users now links to the Slimstat PRO version page, improving clarity around upgrade options. +- **Enhancement**: Added support for the WordPress date format setting for the charts. +- **Fix**: Fatal error in EmailReportsAddon.php for missing `get_plugins` method. +- **Fix**: Prevented PHP warning by checking if 'referer' array key is set in searchterms reports view. +- **Fix**: Fix a database error related to the notes column. +- **Fix**: Prevented horizontal scrolling in the reports area and improved page loading animations by ensuring styles are applied correctly. +- **Fix**: Addressed several user-reported issues to enhance overall stability and user experience. +- **Fix**: Investigate and resolve the "Division by zero" fatal error in `wp-slimstat-db.php` caused by PHP version 8.2.22. Further investigation needed to determine the root cause and provide a fix. + += 5.2.9 - 2024-11-12 = +- **Enhancement**: Ensured compatibility with WordPress version 6.7. +- **Fix**: Resolved the Top Referring Domain Issue. + +[See changelog for all versions](https://raw.githubusercontent.com/wp-slimstat/wp-slimstat/master/CHANGELOG.md). @@ -6,8 +6,8 @@ use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; -// Project-specific Rector configuration for the Salon Pro theme. -// Adjust PhpVersion::PHP_74 to match your actual PHP target if needed. +// Project-specific Rector configuration for WP-SlimStat plugin. +// Configured for PHP 7.4+ compatibility as per project requirements. return static function (RectorConfig $rectorConfig): void { // Paths Rector should analyze and refactor @@ -45,8 +45,8 @@ __DIR__ . '/node_modules', __DIR__ . '/phpstan-cache', __DIR__ . '/languages', - __DIR__ . '/rector.php', __DIR__ . '/src/Dependencies', __DIR__ . '/src/symfony', + __DIR__ . '/rector.php', ]); }; Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Components: Ajax.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Components: DateRangeHelper.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Components: Event.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Components: RemoteRequest.php @@ -9,11 +9,62 @@ exit; } -use SlimStat\Exception\SystemErrorException; class View { /** + * Allowed variable names for extraction. + * Only these keys will be extracted from $args to prevent variable injection. + * + * @var array + */ + private static $allowed_keys = [ + 'data', + 'prevData', + 'chartLabels', + 'translations', + 'args', + 'totals', + 'is_pro', + 'report_id', + 'settings', + 'options', + 'items', + 'title', + 'description', + 'content', + 'filters', + 'columns', + 'rows', + 'pagination', + 'chart_args', + 'chart_data', + 'chart_type', + 'granularity', + 'visitors', + 'pageviews', + 'events', + 'countries', + 'cities', + 'browsers', + 'platforms', + 'screen_sizes', + 'languages', + 'referrers', + 'search_terms', + 'resources', + 'outbound', + 'downloads', + 'notices', + 'message', + 'type', + 'class', + 'notification', + 'notifications', + 'tab', + ]; + + /** * Load a view file and pass data to it. * * @param string|array $view The view path inside views directory @@ -35,11 +86,18 @@ $viewPath = sprintf('%s/views/%s.php', $baseDir, $view); if (!file_exists($viewPath)) { - throw new SystemErrorException(esc_html__('View file not found: ' . $viewPath, 'wp-slimstat')); + throw new \Exception(esc_html__('View file not found: ' . $viewPath, 'wp-slimstat')); } - if (!empty($args)) { - extract($args); + // Make $view_args available to templates (safer than extract) + $view_args = $args; + + // For backward compatibility, extract only allowed keys + // This prevents variable injection attacks while maintaining existing functionality + if (!empty($args) && is_array($args)) { + $safe_args = array_intersect_key($args, array_flip(self::$allowed_keys)); + // phpcs:ignore WordPress.PHP.DontExtract.extract_extract -- Intentionally limited to allowed keys only + extract($safe_args, EXTR_SKIP); } // Return the template if requested @@ -52,7 +110,7 @@ include $viewPath; } } catch (\Exception $exception) { - \SlimStat::log($exception->getMessage(), 'error'); + \wp_slimstat::log($exception->getMessage(), 'error'); } return null; Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src: Controllers Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src: Decorators Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Dependencies: GeoIp2 Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Dependencies: MaxMind @@ -11,31 +11,31 @@ class DataBuckets { - private array $labels = []; + private $labels = []; - private array $prev_labels = []; + private $prev_labels = []; - private array $datasets = ['v1' => [], 'v2' => []]; + private $datasets = ['v1' => [], 'v2' => []]; - private array $datasetsPrev = ['v1' => [], 'v2' => []]; + private $datasetsPrev = ['v1' => [], 'v2' => []]; - private array $totals; + private $totals; - private string $labelFormat; + private $labelFormat; - private string $gran; + private $gran; - private string $tzOffset; + private $tzOffset; - private int $start; + private $start; - private int $end; + private $end; - private int $prevStart; + private $prevStart; - private int $prevEnd; + private $prevEnd; - private int $points; + private $points; public function __construct(string $labelFormat, string $gran, int $start, int $end, int $prevStart, int $prevEnd, array $totals = []) { @@ -184,7 +184,7 @@ $start = new \DateTime('@' . $base); $start = $start->modify('first day of this month')->modify('midnight'); // Guard against invalid/empty $dt - $safeDt = is_numeric($dt) ? (int) $dt : 0; + $safeDt = is_numeric($dt) ? $dt : 0; $target = new \DateTime('@' . $safeDt); if ($target->getTimestamp() < $start->getTimestamp()) { $offset = -1; @@ -233,7 +233,7 @@ { foreach (['v1', 'v2'] as $k) { if (isset($this->datasets[$k][-1])) { - $newKeys = array_map(fn ($key) => $key + 1, array_keys($this->datasets[$k])); + $newKeys = array_map(fn($key) => $key + 1, array_keys($this->datasets[$k])); $this->datasets[$k] = array_combine($newKeys, array_values($this->datasets[$k])); ksort($this->datasets[$k]); if (empty(end($this->datasets[$k]))) { @@ -242,7 +242,7 @@ } if (isset($this->datasetsPrev[$k][-1])) { - $newKeys = array_map(fn ($key) => $key + 1, array_keys($this->datasetsPrev[$k])); + $newKeys = array_map(fn($key) => $key + 1, array_keys($this->datasetsPrev[$k])); $this->datasetsPrev[$k] = array_combine($newKeys, array_values($this->datasetsPrev[$k])); ksort($this->datasetsPrev[$k]); if (empty(end($this->datasetsPrev[$k]))) { Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src: Interfaces Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src: Migration @@ -11,6 +11,7 @@ use SlimStat\Components\View; use SlimStat\Helpers\DataBuckets; +use SlimStat\Utils\Query; class Chart { @@ -20,6 +21,8 @@ private const GRANULARITIES = ['yearly', 'monthly', 'weekly', 'daily', 'hourly']; + private const CHART_TYPES = ['line', 'bar']; + private array $args = []; private array $data = []; @@ -67,6 +70,16 @@ \wp_slimstat_db::init(); } + // Restore filters from args if provided; validate column keys against known schema + if (!empty($args['filters']) && is_array($args['filters'])) { + $allowed_columns = array_keys(\wp_slimstat_db::$columns_names); + foreach ($args['filters'] as $col => $val) { + if (in_array($col, $allowed_columns, true)) { + \wp_slimstat_db::$filters_normalized['columns'][$col] = $val; + } + } + } + \wp_slimstat_db::$filters_normalized['utime']['start'] = $args['start']; \wp_slimstat_db::$filters_normalized['utime']['end'] = $args['end']; \wp_slimstat_db::$filters_normalized['utime']['range'] = $args['end'] - $args['start']; @@ -124,14 +137,33 @@ private function normalizeArgs(array $args): array { $defaults = [ - 'start' => \wp_slimstat_db::$filters_normalized['utime']['start'], - 'end' => \wp_slimstat_db::$filters_normalized['utime']['end'], + 'start' => \wp_slimstat_db::$filters_normalized['utime']['start'], + 'end' => \wp_slimstat_db::$filters_normalized['utime']['end'], + 'chart_type' => 'line', ]; $args = array_merge($defaults, $args); + // Validate chart type + if (!in_array($args['chart_type'], self::CHART_TYPES, true)) { + $args['chart_type'] = 'line'; + } + $args['granularity'] = $this->detectGranularity($args); $args['rangeDays'] = $this->countDays($args['start'], $args['end']); + // Preserve active filters for AJAX requests + if (!isset($args['filters'])) { + $args['filters'] = \wp_slimstat_db::$filters_normalized['columns'] ?? []; + } + + // Ensure chart_data is present with defaults + if (!isset($args['chart_data'])) { + $args['chart_data'] = [ + 'data1' => 'COUNT( ip )', + 'data2' => 'COUNT( DISTINCT ip )', + ]; + } + return $args; } @@ -151,14 +183,14 @@ return 'monthly'; } - if ($diff > 2 * self::DAY) { - return 'daily'; - } - if ($diff > 7 * self::DAY) { return 'weekly'; } + if ($diff > 2 * self::DAY) { + return 'daily'; + } + return 'hourly'; } @@ -168,8 +200,24 @@ $prevArgs = $this->calculatePreviousArgs($args); $sqlInfo = $this->buildSql($args, $prevArgs); - $results = $wpdb->get_results($sqlInfo['sql']); - $totals = $wpdb->get_results($sqlInfo['totalsSql']); + + // Allow caching only if both current and previous ranges end before today + $todayStart = strtotime(date('Y-m-d 00:00:00')); + $canCacheRanges = ($args['end'] < $todayStart && $prevArgs['end'] < $todayStart); + + $rowsQuery = $sqlInfo['query']; + $totalsQuery = $sqlInfo['totalsQuery']; + + if ($rowsQuery instanceof Query) { + $rowsQuery->allowCaching($canCacheRanges, DAY_IN_SECONDS); + } + + if ($totalsQuery instanceof Query) { + $totalsQuery->allowCaching($canCacheRanges, DAY_IN_SECONDS); + } + + $results = $rowsQuery instanceof Query ? $rowsQuery->getAll() : []; + $totals = $totalsQuery instanceof Query ? $totalsQuery->getAll() : []; return $this->processResults( $results, @@ -241,7 +289,17 @@ $prevStart = absint($prevArgs['start']); $prevEnd = absint($prevArgs['end']); - $totalOffsetSeconds = (int) $wpdb->get_var('SELECT TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), NOW())'); + // Build WHERE clause from active filters (excluding time filters) + $filterWhere = $this->buildFilterWhere(); + + // Add chart-specific WHERE clause if provided + if (!empty($args['chart_data']['where'])) { + $chartWhere = $args['chart_data']['where']; + $filterWhere = !empty($filterWhere) ? $filterWhere . ' AND ' . $chartWhere : $chartWhere; + } + + // Use UNIX_TIMESTAMP difference for broad MySQL 5.0.x compatibility + $totalOffsetSeconds = (int) $wpdb->get_var('SELECT UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(UTC_TIMESTAMP())'); $sign = ($totalOffsetSeconds < 0) ? '+' : '-'; $abs = abs($totalOffsetSeconds); $h = floor($abs / 3600); @@ -278,62 +336,99 @@ 'YEAR' => ['label' => 'Y'], ]; - // Note: $data1 and $data2 are already validated and safe - // All timestamps are sanitized as integers - // Table prefix comes from WordPress (safe) - // $tzOffset is calculated from DB query and formatted (safe) - - $sql = " - SELECT - grouped_date AS dt, - v1, - v2, - period - FROM ( - SELECT - MIN(dt) AS dt, - {$data1} AS v1, - {$data2} AS v2, - CASE - WHEN dt BETWEEN {$start} AND {$end} THEN 'current' - ELSE 'previous' - END AS period, - {$dtExpr} AS grouped_date - FROM {$wpdb->prefix}slim_stats - WHERE dt BETWEEN {$prevStart} AND {$prevEnd} - OR dt BETWEEN {$start} AND {$end} - GROUP BY grouped_date, period - ) AS grouped_data - ORDER BY dt, period - "; - - // Total V1 and V2 - $totalsSql = " - SELECT - {$data1} AS v1, - {$data2} AS v2, - CASE - WHEN dt BETWEEN {$start} AND {$end} THEN 'current' - ELSE 'previous' - END AS period - FROM {$wpdb->prefix}slim_stats - WHERE CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$prevStart}) AND FROM_UNIXTIME({$prevEnd}) - OR CONVERT_TZ(FROM_UNIXTIME(dt), '+00:00', '{$tzOffset}') BETWEEN FROM_UNIXTIME({$start}) AND FROM_UNIXTIME({$end}) - GROUP BY period - ORDER BY period - "; + // Build main grouped query via Query builder + $fields = implode(",\n ", [ + $dtExpr . ' AS dt', + 'MIN(dt) AS sort_dt', + $data1 . ' AS v1', + $data2 . ' AS v2', + sprintf("CASE WHEN dt BETWEEN %s AND %s THEN 'current' ELSE 'previous' END AS period", $start, $end), + ]); + + // Wrap the OR time ranges in an extra pair of parentheses so subsequent + // AND filters are applied to the whole time expression instead of + // binding tighter to only the latter OR clause. + $rowsQuery = Query::select($fields) + ->from($wpdb->prefix . 'slim_stats') + ->whereRaw('((dt BETWEEN %d AND %d) OR (dt BETWEEN %d AND %d))', [$prevArgs['start'], $prevArgs['end'], $start, $end]); + + // Apply additional filters if any + if (!empty($filterWhere)) { + $rowsQuery->whereRaw($filterWhere); + } + + $rowsQuery->groupBy($dtExpr . ', period') + ->orderBy('sort_dt ASC, period ASC'); + + // Build totals query via Query builder + // No CONVERT_TZ needed for totals - dt is already stored as UTC timestamp and filters use UTC + $totalsFields = sprintf("%s AS v1, %s AS v2, CASE WHEN dt BETWEEN %s AND %s THEN 'current' ELSE 'previous' END AS period", $data1, $data2, $start, $end); + // Ensure totals WHERE uses grouped OR so filters are applied correctly. + $totalsWhere = '((dt BETWEEN %d AND %d) OR (dt BETWEEN %d AND %d))'; + $totalsQuery = Query::select($totalsFields) + ->from($wpdb->prefix . 'slim_stats') + ->whereRaw($totalsWhere, [$prevArgs['start'], $prevArgs['end'], $start, $end]); + + // Apply additional filters if any + if (!empty($filterWhere)) { + $totalsQuery->whereRaw($filterWhere); + } + + $totalsQuery->groupBy('period') + ->orderBy('period ASC'); return [ - 'sql' => $sql, - 'totalsSql' => $totalsSql, - 'params' => ['label' => $periods[$gran]['label'], 'gran' => $gran], + 'query' => $rowsQuery, + 'totalsQuery' => $totalsQuery, + 'params' => ['label' => $periods[$gran]['label'], 'gran' => $gran], ]; } /** + * Build WHERE clause from active filters (excluding time filters) + * + * @return string SQL WHERE clause conditions or empty string + */ + private function buildFilterWhere(): string + { + if (!class_exists('\wp_slimstat_db')) { + return ''; + } + + // Get active filters (excluding time filters) + if (empty(\wp_slimstat_db::$filters_normalized['columns'])) { + return ''; + } + + $whereClauses = []; + + foreach (\wp_slimstat_db::$filters_normalized['columns'] as $column => $filterData) { + // Skip addon filters + if (false !== strpos($column, 'addon_')) { + continue; + } + + $operator = $filterData[0] ?? 'equals'; + $value = $filterData[1] ?? ''; + + $clause = \wp_slimstat_db::get_single_where_clause($column, $operator, $value); + + if (!empty($clause)) { + $whereClauses[] = $clause; + } + } + + if (empty($whereClauses)) { + return ''; + } + + return implode(' AND ', $whereClauses); + } + + /** * Validates SQL expressions to prevent SQL injection attacks. * Uses a predefined metrics system for maximum security. - * + * * @param string $expression The SQL expression to validate * @return string The safe SQL expression * @throws \Exception If the expression is invalid or potentially malicious @@ -341,82 +436,99 @@ private function validateSqlExpression(string $expression): string { global $wpdb; - + // Remove extra whitespace and normalize $expression = preg_replace('/\s+/', ' ', trim($expression)); - + // Empty expressions default to COUNT(*) if (empty($expression)) { return 'COUNT(*)'; } - + // Define allowed columns from wp_slim_stats table $allowedColumns = [ 'id', 'ip', 'other_ip', 'username', 'email', 'country', 'location', 'city', - 'referer', 'resource', 'searchterms', 'notes', 'visit_id', + 'referer', 'resource', 'searchterms', 'notes', 'visit_id', 'server_latency', 'page_performance', - 'browser', 'browser_version', 'browser_type', 'platform', + 'browser', 'browser_version', 'browser_type', 'platform', 'language', 'fingerprint', 'user_agent', 'resolution', 'screen_width', 'screen_height', 'content_type', 'category', 'author', 'content_id', 'outbound_resource', 'tz_offset', 'dt_out', 'dt' ]; - + // Define allowed aggregate functions $allowedFunctions = ['COUNT', 'SUM', 'AVG', 'MAX', 'MIN']; - + // Strict pattern matching with anchors to prevent bypass attempts // Pattern 1: COUNT(*) or SUM(*) etc (no spaces allowed in function name) if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*\*\s*\)$/i', $expression, $matches)) { $function = strtoupper($matches[1]); return $function . '(*)'; } - + // Pattern 2: COUNT(column) or COUNT( column ) if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*([a-z_][a-z0-9_]*)\s*\)$/i', $expression, $matches)) { $function = strtoupper($matches[1]); $column = strtolower($matches[2]); - + if (!in_array($function, $allowedFunctions, true)) { throw new \Exception(__('Invalid SQL function in chart data expression', 'wp-slimstat')); } - + if (!in_array($column, $allowedColumns, true)) { throw new \Exception(__('Invalid column name in chart data expression', 'wp-slimstat')); } - + // Use esc_sql as additional protection (though column is whitelisted) return $function . '( ' . esc_sql($column) . ' )'; } - + // Pattern 3: COUNT(DISTINCT column) or COUNT( DISTINCT column ) if (preg_match('/^(COUNT|SUM|AVG|MAX|MIN)\s*\(\s*DISTINCT\s+([a-z_][a-z0-9_]*)\s*\)$/i', $expression, $matches)) { $function = strtoupper($matches[1]); $column = strtolower($matches[2]); - + if (!in_array($function, $allowedFunctions, true)) { throw new \Exception(__('Invalid SQL function in chart data expression', 'wp-slimstat')); } - + if (!in_array($column, $allowedColumns, true)) { throw new \Exception(__('Invalid column name in chart data expression', 'wp-slimstat')); } - + // Use esc_sql as additional protection (though column is whitelisted) return $function . '( DISTINCT ' . esc_sql($column) . ' )'; } - + // If none of the patterns match, reject the expression throw new \Exception(__('Invalid SQL expression in chart data. Only whitelisted aggregate functions on valid columns are allowed.', 'wp-slimstat')); } private function processResults(array $rows, array $totals, array $params, int $start, int $end, int $prevStart, int $prevEnd): array { - $buckets = new DataBuckets($params['label'], $params['gran'], $start, $end, $prevStart, $prevEnd, $totals); + // Normalize totals to array of stdClass for backward compatibility + $totalsObjects = array_map(function ($t) { + if (is_object($t)) { + return $t; + } + + $o = new \stdClass(); + $o->v1 = isset($t['v1']) ? (int) $t['v1'] : 0; + $o->v2 = isset($t['v2']) ? (int) $t['v2'] : 0; + $o->period = isset($t['period']) ? (string) $t['period'] : ''; + return $o; + }, $totals); + + $buckets = new DataBuckets($params['label'], $params['gran'], $start, $end, $prevStart, $prevEnd, $totalsObjects); foreach ($rows as $row) { - $buckets->addRow((int) $row->dt, (int) $row->v1, (int) $row->v2, (string) $row->period); + $dt = (int) (is_object($row) ? $row->dt : ($row['dt'] ?? 0)); + $v1 = (int) (is_object($row) ? $row->v1 : ($row['v1'] ?? 0)); + $v2 = (int) (is_object($row) ? $row->v2 : ($row['v2'] ?? 0)); + $period = (string) (is_object($row) ? $row->period : ($row['period'] ?? '')); + $buckets->addRow($dt, $v1, $v2, $period); } return $buckets->toArray(); @@ -438,14 +550,14 @@ plugins_url('/admin/assets/js/chartjs/chart.min.js', SLIMSTAT_FILE), [], '4.2.1', - false + true ); wp_enqueue_script( 'slimstat_chart', plugins_url('/admin/assets/js/slimstat-chart.js', SLIMSTAT_FILE), ['slimstat_chartjs'], '1.0', - false + true ); wp_localize_script('slimstat_chart', 'slimstat_chart_vars', [ // Use a relative admin-ajax path for the admin chart to avoid cross-origin issues in dev setups @@ -468,4 +580,14 @@ 'translations' => $this->translations, ]); } + + /** + * Get supported chart types + * + * @return array<string> + */ + public static function get_supported_chart_types(): array + { + return self::CHART_TYPES; + } } Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Providers: IPHashProvider.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Providers: RestApiManager.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/src/Providers: RESTService.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src: Reports Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Services: Admin @@ -195,6 +195,7 @@ protected static function _get_user_agent() { + $user_agent = (empty($_SERVER['HTTP_USER_AGENT']) ? '' : trim($_SERVER['HTTP_USER_AGENT'])); $real_user_agent = ''; if (!empty($_SERVER['HTTP_X_DEVICE_USER_AGENT'])) { Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Services: Compliance Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Services: CronEventManager.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Services: GDPRService.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/src/Services: GeoIP.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Services: Geolocation @@ -2,7 +2,7 @@ namespace SlimStat\Services; -use SlimStat\Utils\MaxMindReader; +use SlimStat\Dependencies\GeoIp2\Database\Reader; class GeoService { @@ -34,7 +34,10 @@ public function getPack() { - return empty($this->pack) ? GeoIP::get_pack() : $this->pack; + if (!empty($this->pack)) { + return $this->pack; + } + return ('on' == \wp_slimstat::$settings['geolocation_country']) ? 'country' : 'city'; } public function setEnableMaxmind($enableMaxmind = false) @@ -94,51 +97,50 @@ return false; } - public function download() - { - try { - if ($this->isGeoIPEnabled()) { - - $args = [ - 'update' => $this->update, - ]; - - if ($this->isMaxMindEnabled() && !empty($this->getMaxMindLicenseKey())) { - $args['enable_maxmind'] = 'on'; - $args['maxmind_license_key'] = $this->getMaxMindLicenseKey(); - } - - $response = GeoIP::download($this->getPack(), $args); - } else { - $response = [ - 'status' => false, - 'error' => __('GeoIP is disabled. Please first choose GeoIP Database Source and save settings!', 'wp-slimstat'), + public function download() + { + try { + $provider = \wp_slimstat::$settings['geolocation_provider'] ?? 'maxmind'; + if (in_array($provider, ['maxmind', 'dbip'], true)) { + // GeolocationService reads settings automatically + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + $ok = $service->updateDatabase(); + return [ + 'status' => (bool) $ok, + 'notice' => $ok ? __('GeoIP Database Successfully Updated!', 'wp-slimstat') : __('Failed to update GeoIP Database.', 'wp-slimstat'), ]; } + return [ 'status' => false, 'error' => __('GeoIP is disabled. Please choose a DB-based provider and save settings.', 'wp-slimstat') ]; } catch (\Exception $exception) { $this->logError($exception->getMessage()); - - $response = [ - 'status' => false, - 'error' => $exception->getMessage(), - ]; + return [ 'status' => false, 'error' => $exception->getMessage() ]; } - - return $response; } - /** - * @throws \Exception - */ - public function checkDatabase() - { - try { - if (!GeoIP::database_exists()) { + /** + * @throws \Exception + */ + public function checkDatabase() + { + try { + $provider = \wp_slimstat::$settings['geolocation_provider'] ?? 'maxmind'; + // GeolocationService reads settings automatically + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + $dbPath = $service->getProvider()->getDbPath(); + if (!file_exists($dbPath)) { throw new \Exception(__('GeoIP database not found!', 'wp-slimstat')); } - $reader = new MaxMindReader(GeoIP::get_database_file()); - $reader->get($this->getUserIP()); + $reader = new Reader($dbPath); + $ip = $this->getUserIP(); + + // Determine which method to use based on database type + $precision = $this->getPack(); + if ('city' === $precision) { + $reader->city($ip); + } else { + $reader->country($ip); + } $response = [ 'status' => true, @@ -161,11 +163,14 @@ wp_clear_scheduled_hook('wp_slimstat_update_geoip_database'); } - public function deleteDatabaseFile() - { - if (GeoIP::database_exists()) { - $databaseFilePath = GeoIP::get_database_file(); - @unlink($databaseFilePath); + public function deleteDatabaseFile() + { + $provider = \wp_slimstat::$settings['geolocation_provider'] ?? 'maxmind'; + // GeolocationService reads settings automatically + $service = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); + $dbPath = $service->getProvider()->getDbPath(); + if (is_file($dbPath)) { + @unlink($dbPath); } } Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Services: Privacy Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Services: Privacy.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src: Tracker Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Utils: Consent.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/src/Utils: InvalidDatabaseException.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/src/Utils: MaxMindDecoder.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/src/Utils: MaxMindMetadata.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/src/Utils: MaxMindReader.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.3.5/src/Utils: MaxMindUtil.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Utils: Query.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src/Utils: Request.php Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/src: view @@ -6,8 +6,281 @@ $baseDir = dirname($vendorDir); return array( + 'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'BrowscapPHP\\Browscap' => $vendorDir . '/veronalabs/browscap-php/src/Browscap.php', + 'BrowscapPHP\\BrowscapInterface' => $vendorDir . '/veronalabs/browscap-php/src/BrowscapInterface.php', + 'BrowscapPHP\\BrowscapUpdater' => $vendorDir . '/veronalabs/browscap-php/src/BrowscapUpdater.php', + 'BrowscapPHP\\BrowscapUpdaterInterface' => $vendorDir . '/veronalabs/browscap-php/src/BrowscapUpdaterInterface.php', + 'BrowscapPHP\\Cache\\BrowscapCache' => $vendorDir . '/veronalabs/browscap-php/src/Cache/BrowscapCache.php', + 'BrowscapPHP\\Cache\\BrowscapCacheInterface' => $vendorDir . '/veronalabs/browscap-php/src/Cache/BrowscapCacheInterface.php', + 'BrowscapPHP\\Command\\CheckUpdateCommand' => $vendorDir . '/veronalabs/browscap-php/src/Command/CheckUpdateCommand.php', + 'BrowscapPHP\\Command\\ConvertCommand' => $vendorDir . '/veronalabs/browscap-php/src/Command/ConvertCommand.php', + 'BrowscapPHP\\Command\\FetchCommand' => $vendorDir . '/veronalabs/browscap-php/src/Command/FetchCommand.php', + 'BrowscapPHP\\Command\\ParserCommand' => $vendorDir . '/veronalabs/browscap-php/src/Command/ParserCommand.php', + 'BrowscapPHP\\Command\\UpdateCommand' => $vendorDir . '/veronalabs/browscap-php/src/Command/UpdateCommand.php', + 'BrowscapPHP\\Data\\PropertyFormatter' => $vendorDir . '/veronalabs/browscap-php/src/Data/PropertyFormatter.php', + 'BrowscapPHP\\Data\\PropertyHolder' => $vendorDir . '/veronalabs/browscap-php/src/Data/PropertyHolder.php', + 'BrowscapPHP\\Exception' => $vendorDir . '/veronalabs/browscap-php/src/Exception.php', + 'BrowscapPHP\\Exception\\DomainException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/DomainException.php', + 'BrowscapPHP\\Exception\\ErrorCachedVersionException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/ErrorCachedVersionException.php', + 'BrowscapPHP\\Exception\\ErrorReadingFileException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/ErrorReadingFileException.php', + 'BrowscapPHP\\Exception\\FetcherException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/FetcherException.php', + 'BrowscapPHP\\Exception\\FileNameMissingException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/FileNameMissingException.php', + 'BrowscapPHP\\Exception\\FileNotFoundException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/FileNotFoundException.php', + 'BrowscapPHP\\Exception\\InvalidArgumentException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/InvalidArgumentException.php', + 'BrowscapPHP\\Exception\\NoCachedVersionException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/NoCachedVersionException.php', + 'BrowscapPHP\\Exception\\NoNewVersionException' => $vendorDir . '/veronalabs/browscap-php/src/Exception/NoNewVersionException.php', + 'BrowscapPHP\\Formatter\\FormatterInterface' => $vendorDir . '/veronalabs/browscap-php/src/Formatter/FormatterInterface.php', + 'BrowscapPHP\\Formatter\\LegacyFormatter' => $vendorDir . '/veronalabs/browscap-php/src/Formatter/LegacyFormatter.php', + 'BrowscapPHP\\Formatter\\PhpGetBrowser' => $vendorDir . '/veronalabs/browscap-php/src/Formatter/PhpGetBrowser.php', + 'BrowscapPHP\\Helper\\Converter' => $vendorDir . '/veronalabs/browscap-php/src/Helper/Converter.php', + 'BrowscapPHP\\Helper\\ConverterInterface' => $vendorDir . '/veronalabs/browscap-php/src/Helper/ConverterInterface.php', + 'BrowscapPHP\\Helper\\Exception' => $vendorDir . '/veronalabs/browscap-php/src/Helper/Exception.php', + 'BrowscapPHP\\Helper\\Filesystem' => $vendorDir . '/veronalabs/browscap-php/src/Helper/Filesystem.php', + 'BrowscapPHP\\Helper\\IniLoader' => $vendorDir . '/veronalabs/browscap-php/src/Helper/IniLoader.php', + 'BrowscapPHP\\Helper\\IniLoaderInterface' => $vendorDir . '/veronalabs/browscap-php/src/Helper/IniLoaderInterface.php', + 'BrowscapPHP\\Helper\\LoggerHelper' => $vendorDir . '/veronalabs/browscap-php/src/Helper/LoggerHelper.php', + 'BrowscapPHP\\Helper\\Quoter' => $vendorDir . '/veronalabs/browscap-php/src/Helper/Quoter.php', + 'BrowscapPHP\\Helper\\QuoterInterface' => $vendorDir . '/veronalabs/browscap-php/src/Helper/QuoterInterface.php', + 'BrowscapPHP\\Helper\\Support' => $vendorDir . '/veronalabs/browscap-php/src/Helper/Support.php', + 'BrowscapPHP\\Helper\\SupportInterface' => $vendorDir . '/veronalabs/browscap-php/src/Helper/SupportInterface.php', + 'BrowscapPHP\\IniParser\\IniParser' => $vendorDir . '/veronalabs/browscap-php/src/IniParser/IniParser.php', + 'BrowscapPHP\\IniParser\\ParserInterface' => $vendorDir . '/veronalabs/browscap-php/src/IniParser/ParserInterface.php', + 'BrowscapPHP\\Parser\\Helper\\GetData' => $vendorDir . '/veronalabs/browscap-php/src/Parser/Helper/GetData.php', + 'BrowscapPHP\\Parser\\Helper\\GetDataInterface' => $vendorDir . '/veronalabs/browscap-php/src/Parser/Helper/GetDataInterface.php', + 'BrowscapPHP\\Parser\\Helper\\GetPattern' => $vendorDir . '/veronalabs/browscap-php/src/Parser/Helper/GetPattern.php', + 'BrowscapPHP\\Parser\\Helper\\GetPatternInterface' => $vendorDir . '/veronalabs/browscap-php/src/Parser/Helper/GetPatternInterface.php', + 'BrowscapPHP\\Parser\\Helper\\Pattern' => $vendorDir . '/veronalabs/browscap-php/src/Parser/Helper/Pattern.php', + 'BrowscapPHP\\Parser\\Helper\\SubKey' => $vendorDir . '/veronalabs/browscap-php/src/Parser/Helper/SubKey.php', + 'BrowscapPHP\\Parser\\Ini' => $vendorDir . '/veronalabs/browscap-php/src/Parser/Ini.php', + 'BrowscapPHP\\Parser\\ParserInterface' => $vendorDir . '/veronalabs/browscap-php/src/Parser/ParserInterface.php', 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'GuzzleHttp\\BodySummarizer' => $vendorDir . '/guzzlehttp/guzzle/src/BodySummarizer.php', + 'GuzzleHttp\\BodySummarizerInterface' => $vendorDir . '/guzzlehttp/guzzle/src/BodySummarizerInterface.php', + 'GuzzleHttp\\Client' => $vendorDir . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\ClientTrait' => $vendorDir . '/guzzlehttp/guzzle/src/ClientTrait.php', + 'GuzzleHttp\\Cookie\\CookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\InvalidArgumentException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php', + 'GuzzleHttp\\Exception\\RequestException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\ServerException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => $vendorDir . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\HeaderProcessor' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php', + 'GuzzleHttp\\Handler\\MockHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => $vendorDir . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\MessageFormatterInterface' => $vendorDir . '/guzzlehttp/guzzle/src/MessageFormatterInterface.php', + 'GuzzleHttp\\Middleware' => $vendorDir . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => $vendorDir . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => $vendorDir . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => $vendorDir . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => $vendorDir . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\Create' => $vendorDir . '/guzzlehttp/promises/src/Create.php', + 'GuzzleHttp\\Promise\\Each' => $vendorDir . '/guzzlehttp/promises/src/Each.php', + 'GuzzleHttp\\Promise\\EachPromise' => $vendorDir . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => $vendorDir . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Is' => $vendorDir . '/guzzlehttp/promises/src/Is.php', + 'GuzzleHttp\\Promise\\Promise' => $vendorDir . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => $vendorDir . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => $vendorDir . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => $vendorDir . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => $vendorDir . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => $vendorDir . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => $vendorDir . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Promise\\Utils' => $vendorDir . '/guzzlehttp/promises/src/Utils.php', + 'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => $vendorDir . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => $vendorDir . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => $vendorDir . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php', + 'GuzzleHttp\\Psr7\\FnStream' => $vendorDir . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\Header' => $vendorDir . '/guzzlehttp/psr7/src/Header.php', + 'GuzzleHttp\\Psr7\\HttpFactory' => $vendorDir . '/guzzlehttp/psr7/src/HttpFactory.php', + 'GuzzleHttp\\Psr7\\InflateStream' => $vendorDir . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => $vendorDir . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => $vendorDir . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\Message' => $vendorDir . '/guzzlehttp/psr7/src/Message.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => $vendorDir . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MimeType' => $vendorDir . '/guzzlehttp/psr7/src/MimeType.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => $vendorDir . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => $vendorDir . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => $vendorDir . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Query' => $vendorDir . '/guzzlehttp/psr7/src/Query.php', + 'GuzzleHttp\\Psr7\\Request' => $vendorDir . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => $vendorDir . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\Rfc7230' => $vendorDir . '/guzzlehttp/psr7/src/Rfc7230.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => $vendorDir . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => $vendorDir . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => $vendorDir . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => $vendorDir . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => $vendorDir . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriComparator' => $vendorDir . '/guzzlehttp/psr7/src/UriComparator.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => $vendorDir . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => $vendorDir . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\Psr7\\Utils' => $vendorDir . '/guzzlehttp/psr7/src/Utils.php', + 'GuzzleHttp\\RedirectMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => $vendorDir . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => $vendorDir . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => $vendorDir . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\Utils' => $vendorDir . '/guzzlehttp/guzzle/src/Utils.php', + 'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'League\\Flysystem\\Config' => $vendorDir . '/league/flysystem/src/Config.php', + 'League\\Flysystem\\CorruptedPathDetected' => $vendorDir . '/league/flysystem/src/CorruptedPathDetected.php', + 'League\\Flysystem\\DirectoryAttributes' => $vendorDir . '/league/flysystem/src/DirectoryAttributes.php', + 'League\\Flysystem\\DirectoryListing' => $vendorDir . '/league/flysystem/src/DirectoryListing.php', + 'League\\Flysystem\\FileAttributes' => $vendorDir . '/league/flysystem/src/FileAttributes.php', + 'League\\Flysystem\\Filesystem' => $vendorDir . '/league/flysystem/src/Filesystem.php', + 'League\\Flysystem\\FilesystemAdapter' => $vendorDir . '/league/flysystem/src/FilesystemAdapter.php', + 'League\\Flysystem\\FilesystemException' => $vendorDir . '/league/flysystem/src/FilesystemException.php', + 'League\\Flysystem\\FilesystemOperationFailed' => $vendorDir . '/league/flysystem/src/FilesystemOperationFailed.php', + 'League\\Flysystem\\FilesystemOperator' => $vendorDir . '/league/flysystem/src/FilesystemOperator.php', + 'League\\Flysystem\\FilesystemReader' => $vendorDir . '/league/flysystem/src/FilesystemReader.php', + 'League\\Flysystem\\FilesystemWriter' => $vendorDir . '/league/flysystem/src/FilesystemWriter.php', + 'League\\Flysystem\\InvalidStreamProvided' => $vendorDir . '/league/flysystem/src/InvalidStreamProvided.php', + 'League\\Flysystem\\InvalidVisibilityProvided' => $vendorDir . '/league/flysystem/src/InvalidVisibilityProvided.php', + 'League\\Flysystem\\Local\\LocalFilesystemAdapter' => $vendorDir . '/league/flysystem/src/Local/LocalFilesystemAdapter.php', + 'League\\Flysystem\\MountManager' => $vendorDir . '/league/flysystem/src/MountManager.php', + 'League\\Flysystem\\PathNormalizer' => $vendorDir . '/league/flysystem/src/PathNormalizer.php', + 'League\\Flysystem\\PathPrefixer' => $vendorDir . '/league/flysystem/src/PathPrefixer.php', + 'League\\Flysystem\\PathTraversalDetected' => $vendorDir . '/league/flysystem/src/PathTraversalDetected.php', + 'League\\Flysystem\\PortableVisibilityGuard' => $vendorDir . '/league/flysystem/src/PortableVisibilityGuard.php', + 'League\\Flysystem\\ProxyArrayAccessToProperties' => $vendorDir . '/league/flysystem/src/ProxyArrayAccessToProperties.php', + 'League\\Flysystem\\StorageAttributes' => $vendorDir . '/league/flysystem/src/StorageAttributes.php', + 'League\\Flysystem\\SymbolicLinkEncountered' => $vendorDir . '/league/flysystem/src/SymbolicLinkEncountered.php', + 'League\\Flysystem\\UnableToCheckFileExistence' => $vendorDir . '/league/flysystem/src/UnableToCheckFileExistence.php', + 'League\\Flysystem\\UnableToCopyFile' => $vendorDir . '/league/flysystem/src/UnableToCopyFile.php', + 'League\\Flysystem\\UnableToCreateDirectory' => $vendorDir . '/league/flysystem/src/UnableToCreateDirectory.php', + 'League\\Flysystem\\UnableToDeleteDirectory' => $vendorDir . '/league/flysystem/src/UnableToDeleteDirectory.php', + 'League\\Flysystem\\UnableToDeleteFile' => $vendorDir . '/league/flysystem/src/UnableToDeleteFile.php', + 'League\\Flysystem\\UnableToMountFilesystem' => $vendorDir . '/league/flysystem/src/UnableToMountFilesystem.php', + 'League\\Flysystem\\UnableToMoveFile' => $vendorDir . '/league/flysystem/src/UnableToMoveFile.php', + 'League\\Flysystem\\UnableToReadFile' => $vendorDir . '/league/flysystem/src/UnableToReadFile.php', + 'League\\Flysystem\\UnableToResolveFilesystemMount' => $vendorDir . '/league/flysystem/src/UnableToResolveFilesystemMount.php', + 'League\\Flysystem\\UnableToRetrieveMetadata' => $vendorDir . '/league/flysystem/src/UnableToRetrieveMetadata.php', + 'League\\Flysystem\\UnableToSetVisibility' => $vendorDir . '/league/flysystem/src/UnableToSetVisibility.php', + 'League\\Flysystem\\UnableToWriteFile' => $vendorDir . '/league/flysystem/src/UnableToWriteFile.php', + 'League\\Flysystem\\UnixVisibility\\PortableVisibilityConverter' => $vendorDir . '/league/flysystem/src/UnixVisibility/PortableVisibilityConverter.php', + 'League\\Flysystem\\UnixVisibility\\VisibilityConverter' => $vendorDir . '/league/flysystem/src/UnixVisibility/VisibilityConverter.php', + 'League\\Flysystem\\UnreadableFileEncountered' => $vendorDir . '/league/flysystem/src/UnreadableFileEncountered.php', + 'League\\Flysystem\\Visibility' => $vendorDir . '/league/flysystem/src/Visibility.php', + 'League\\Flysystem\\WhitespacePathNormalizer' => $vendorDir . '/league/flysystem/src/WhitespacePathNormalizer.php', + 'League\\MimeTypeDetection\\EmptyExtensionToMimeTypeMap' => $vendorDir . '/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\ExtensionLookup' => $vendorDir . '/league/mime-type-detection/src/ExtensionLookup.php', + 'League\\MimeTypeDetection\\ExtensionMimeTypeDetector' => $vendorDir . '/league/mime-type-detection/src/ExtensionMimeTypeDetector.php', + 'League\\MimeTypeDetection\\ExtensionToMimeTypeMap' => $vendorDir . '/league/mime-type-detection/src/ExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\FinfoMimeTypeDetector' => $vendorDir . '/league/mime-type-detection/src/FinfoMimeTypeDetector.php', + 'League\\MimeTypeDetection\\GeneratedExtensionToMimeTypeMap' => $vendorDir . '/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\MimeTypeDetector' => $vendorDir . '/league/mime-type-detection/src/MimeTypeDetector.php', + 'League\\MimeTypeDetection\\OverridingExtensionToMimeTypeMap' => $vendorDir . '/league/mime-type-detection/src/OverridingExtensionToMimeTypeMap.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Apc' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Apc.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Apc' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/Apc.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Couchbase' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/Couchbase.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Flysystem' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/Flysystem.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Memcached' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/Memcached.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\MemoryStore' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/MemoryStore.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Redis' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/Redis.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\SQL' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/SQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Utils\\PrefixKeys' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixKeys.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Utils\\PrefixReset' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixReset.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Couchbase' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Couchbase.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Flysystem' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Flysystem.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Memcached' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Memcached.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\MemoryStore' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/MemoryStore.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\MySQL' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/MySQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\PostgreSQL' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/PostgreSQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Redis' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/Redis.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\SQL' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/SQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\SQLite' => $vendorDir . '/matthiasmullie/scrapbook/src/Adapters/SQLite.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\BufferedStore' => $vendorDir . '/matthiasmullie/scrapbook/src/Buffered/BufferedStore.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\TransactionalStore' => $vendorDir . '/matthiasmullie/scrapbook/src/Buffered/TransactionalStore.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\Buffer' => $vendorDir . '/matthiasmullie/scrapbook/src/Buffered/Utils/Buffer.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\BufferCollection' => $vendorDir . '/matthiasmullie/scrapbook/src/Buffered/Utils/BufferCollection.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\Defer' => $vendorDir . '/matthiasmullie/scrapbook/src/Buffered/Utils/Defer.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\Transaction' => $vendorDir . '/matthiasmullie/scrapbook/src/Buffered/Utils/Transaction.php', + 'MatthiasMullie\\Scrapbook\\Exception\\Exception' => $vendorDir . '/matthiasmullie/scrapbook/src/Exception/Exception.php', + 'MatthiasMullie\\Scrapbook\\Exception\\InvalidCollection' => $vendorDir . '/matthiasmullie/scrapbook/src/Exception/InvalidCollection.php', + 'MatthiasMullie\\Scrapbook\\Exception\\InvalidKey' => $vendorDir . '/matthiasmullie/scrapbook/src/Exception/InvalidKey.php', + 'MatthiasMullie\\Scrapbook\\Exception\\OperationFailed' => $vendorDir . '/matthiasmullie/scrapbook/src/Exception/OperationFailed.php', + 'MatthiasMullie\\Scrapbook\\Exception\\ServerUnhealthy' => $vendorDir . '/matthiasmullie/scrapbook/src/Exception/ServerUnhealthy.php', + 'MatthiasMullie\\Scrapbook\\Exception\\UnbegunTransaction' => $vendorDir . '/matthiasmullie/scrapbook/src/Exception/UnbegunTransaction.php', + 'MatthiasMullie\\Scrapbook\\Exception\\UncommittedTransaction' => $vendorDir . '/matthiasmullie/scrapbook/src/Exception/UncommittedTransaction.php', + 'MatthiasMullie\\Scrapbook\\KeyValueStore' => $vendorDir . '/matthiasmullie/scrapbook/src/KeyValueStore.php', + 'MatthiasMullie\\Scrapbook\\Psr16\\InvalidArgumentException' => $vendorDir . '/matthiasmullie/scrapbook/src/Psr16/InvalidArgumentException.php', + 'MatthiasMullie\\Scrapbook\\Psr16\\SimpleCache' => $vendorDir . '/matthiasmullie/scrapbook/src/Psr16/SimpleCache.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\InvalidArgumentException' => $vendorDir . '/matthiasmullie/scrapbook/src/Psr6/InvalidArgumentException.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\Item' => $vendorDir . '/matthiasmullie/scrapbook/src/Psr6/Item.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\Pool' => $vendorDir . '/matthiasmullie/scrapbook/src/Psr6/Pool.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\Repository' => $vendorDir . '/matthiasmullie/scrapbook/src/Psr6/Repository.php', + 'MatthiasMullie\\Scrapbook\\Scale\\Shard' => $vendorDir . '/matthiasmullie/scrapbook/src/Scale/Shard.php', + 'MatthiasMullie\\Scrapbook\\Scale\\StampedeProtector' => $vendorDir . '/matthiasmullie/scrapbook/src/Scale/StampedeProtector.php', + 'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => $vendorDir . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Client\\ClientExceptionInterface' => $vendorDir . '/psr/http-client/src/ClientExceptionInterface.php', + 'Psr\\Http\\Client\\ClientInterface' => $vendorDir . '/psr/http-client/src/ClientInterface.php', + 'Psr\\Http\\Client\\NetworkExceptionInterface' => $vendorDir . '/psr/http-client/src/NetworkExceptionInterface.php', + 'Psr\\Http\\Client\\RequestExceptionInterface' => $vendorDir . '/psr/http-client/src/RequestExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => $vendorDir . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/RequestFactoryInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => $vendorDir . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseFactoryInterface' => $vendorDir . '/psr/http-factory/src/ResponseFactoryInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => $vendorDir . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestFactoryInterface' => $vendorDir . '/psr/http-factory/src/ServerRequestFactoryInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => $vendorDir . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamFactoryInterface' => $vendorDir . '/psr/http-factory/src/StreamFactoryInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => $vendorDir . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileFactoryInterface' => $vendorDir . '/psr/http-factory/src/UploadedFileFactoryInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => $vendorDir . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriFactoryInterface' => $vendorDir . '/psr/http-factory/src/UriFactoryInterface.php', + 'Psr\\Http\\Message\\UriInterface' => $vendorDir . '/psr/http-message/src/UriInterface.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/DummyTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php', + 'Psr\\SimpleCache\\CacheException' => $vendorDir . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => $vendorDir . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => $vendorDir . '/psr/simple-cache/src/InvalidArgumentException.php', + 'SlimStat\\Components\\Ajax' => $baseDir . '/src/Components/Ajax.php', + 'SlimStat\\Components\\DateRangeHelper' => $baseDir . '/src/Components/DateRangeHelper.php', + 'SlimStat\\Components\\Event' => $baseDir . '/src/Components/Event.php', + 'SlimStat\\Components\\RemoteRequest' => $baseDir . '/src/Components/RemoteRequest.php', 'SlimStat\\Components\\View' => $baseDir . '/src/Components/View.php', + 'SlimStat\\Controllers\\Rest\\ConsentChangeRestController' => $baseDir . '/src/Controllers/Rest/ConsentChangeRestController.php', + 'SlimStat\\Controllers\\Rest\\ConsentHealthRestController' => $baseDir . '/src/Controllers/Rest/ConsentHealthRestController.php', + 'SlimStat\\Controllers\\Rest\\GDPRBannerRestController' => $baseDir . '/src/Controllers/Rest/GDPRBannerRestController.php', + 'SlimStat\\Controllers\\Rest\\TrackingRestController' => $baseDir . '/src/Controllers/Rest/TrackingRestController.php', + 'SlimStat\\Decorators\\NotificationDecorator' => $baseDir . '/src/Decorators/NotificationDecorator.php', 'SlimStat\\Dependencies\\BrowscapPHP\\Browscap' => $baseDir . '/src/Dependencies/BrowscapPHP/Browscap.php', 'SlimStat\\Dependencies\\BrowscapPHP\\BrowscapInterface' => $baseDir . '/src/Dependencies/BrowscapPHP/BrowscapInterface.php', 'SlimStat\\Dependencies\\BrowscapPHP\\BrowscapUpdater' => $baseDir . '/src/Dependencies/BrowscapPHP/BrowscapUpdater.php', @@ -55,6 +328,37 @@ 'SlimStat\\Dependencies\\BrowscapPHP\\Parser\\Helper\\SubKey' => $baseDir . '/src/Dependencies/BrowscapPHP/Parser/Helper/SubKey.php', 'SlimStat\\Dependencies\\BrowscapPHP\\Parser\\Ini' => $baseDir . '/src/Dependencies/BrowscapPHP/Parser/Ini.php', 'SlimStat\\Dependencies\\BrowscapPHP\\Parser\\ParserInterface' => $baseDir . '/src/Dependencies/BrowscapPHP/Parser/ParserInterface.php', + 'SlimStat\\Dependencies\\GeoIp2\\Database\\Reader' => $baseDir . '/src/Dependencies/GeoIp2/Database/Reader.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\AddressNotFoundException' => $baseDir . '/src/Dependencies/GeoIp2/Exception/AddressNotFoundException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\AuthenticationException' => $baseDir . '/src/Dependencies/GeoIp2/Exception/AuthenticationException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\GeoIp2Exception' => $baseDir . '/src/Dependencies/GeoIp2/Exception/GeoIp2Exception.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\HttpException' => $baseDir . '/src/Dependencies/GeoIp2/Exception/HttpException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\InvalidRequestException' => $baseDir . '/src/Dependencies/GeoIp2/Exception/InvalidRequestException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\OutOfQueriesException' => $baseDir . '/src/Dependencies/GeoIp2/Exception/OutOfQueriesException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\AbstractModel' => $baseDir . '/src/Dependencies/GeoIp2/Model/AbstractModel.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\AnonymousIp' => $baseDir . '/src/Dependencies/GeoIp2/Model/AnonymousIp.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Asn' => $baseDir . '/src/Dependencies/GeoIp2/Model/Asn.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\City' => $baseDir . '/src/Dependencies/GeoIp2/Model/City.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\ConnectionType' => $baseDir . '/src/Dependencies/GeoIp2/Model/ConnectionType.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Country' => $baseDir . '/src/Dependencies/GeoIp2/Model/Country.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Domain' => $baseDir . '/src/Dependencies/GeoIp2/Model/Domain.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Enterprise' => $baseDir . '/src/Dependencies/GeoIp2/Model/Enterprise.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Insights' => $baseDir . '/src/Dependencies/GeoIp2/Model/Insights.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Isp' => $baseDir . '/src/Dependencies/GeoIp2/Model/Isp.php', + 'SlimStat\\Dependencies\\GeoIp2\\ProviderInterface' => $baseDir . '/src/Dependencies/GeoIp2/ProviderInterface.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\AbstractPlaceRecord' => $baseDir . '/src/Dependencies/GeoIp2/Record/AbstractPlaceRecord.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\AbstractRecord' => $baseDir . '/src/Dependencies/GeoIp2/Record/AbstractRecord.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\City' => $baseDir . '/src/Dependencies/GeoIp2/Record/City.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Continent' => $baseDir . '/src/Dependencies/GeoIp2/Record/Continent.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Country' => $baseDir . '/src/Dependencies/GeoIp2/Record/Country.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Location' => $baseDir . '/src/Dependencies/GeoIp2/Record/Location.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\MaxMind' => $baseDir . '/src/Dependencies/GeoIp2/Record/MaxMind.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Postal' => $baseDir . '/src/Dependencies/GeoIp2/Record/Postal.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\RepresentedCountry' => $baseDir . '/src/Dependencies/GeoIp2/Record/RepresentedCountry.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Subdivision' => $baseDir . '/src/Dependencies/GeoIp2/Record/Subdivision.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Traits' => $baseDir . '/src/Dependencies/GeoIp2/Record/Traits.php', + 'SlimStat\\Dependencies\\GeoIp2\\Util' => $baseDir . '/src/Dependencies/GeoIp2/Util.php', + 'SlimStat\\Dependencies\\GeoIp2\\WebService\\Client' => $baseDir . '/src/Dependencies/GeoIp2/WebService/Client.php', 'SlimStat\\Dependencies\\GuzzleHttp\\BodySummarizer' => $baseDir . '/src/Dependencies/GuzzleHttp/BodySummarizer.php', 'SlimStat\\Dependencies\\GuzzleHttp\\BodySummarizerInterface' => $baseDir . '/src/Dependencies/GuzzleHttp/BodySummarizerInterface.php', 'SlimStat\\Dependencies\\GuzzleHttp\\Client' => $baseDir . '/src/Dependencies/GuzzleHttp/Client.php', @@ -230,6 +534,23 @@ 'SlimStat\\Dependencies\\MatthiasMullie\\Scrapbook\\Psr6\\Repository' => $baseDir . '/src/Dependencies/MatthiasMullie/Scrapbook/Psr6/Repository.php', 'SlimStat\\Dependencies\\MatthiasMullie\\Scrapbook\\Scale\\Shard' => $baseDir . '/src/Dependencies/MatthiasMullie/Scrapbook/Scale/Shard.php', 'SlimStat\\Dependencies\\MatthiasMullie\\Scrapbook\\Scale\\StampedeProtector' => $baseDir . '/src/Dependencies/MatthiasMullie/Scrapbook/Scale/StampedeProtector.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader' => $baseDir . '/src/Dependencies/MaxMind/Db/Reader.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\Decoder' => $baseDir . '/src/Dependencies/MaxMind/Db/Reader/Decoder.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\InvalidDatabaseException' => $baseDir . '/src/Dependencies/MaxMind/Db/Reader/InvalidDatabaseException.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\Metadata' => $baseDir . '/src/Dependencies/MaxMind/Db/Reader/Metadata.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\Util' => $baseDir . '/src/Dependencies/MaxMind/Db/Reader/Util.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\AuthenticationException' => $baseDir . '/src/Dependencies/MaxMind/WebService/AuthenticationException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Client' => $baseDir . '/src/Dependencies/MaxMind/WebService/Client.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\HttpException' => $baseDir . '/src/Dependencies/MaxMind/WebService/HttpException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Http\\CurlRequest' => $baseDir . '/src/Dependencies/MaxMind/WebService/Http/CurlRequest.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Http\\Request' => $baseDir . '/src/Dependencies/MaxMind/WebService/Http/Request.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Http\\RequestFactory' => $baseDir . '/src/Dependencies/MaxMind/WebService/Http/RequestFactory.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\InsufficientFundsException' => $baseDir . '/src/Dependencies/MaxMind/WebService/InsufficientFundsException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\InvalidInputException' => $baseDir . '/src/Dependencies/MaxMind/WebService/InvalidInputException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\InvalidRequestException' => $baseDir . '/src/Dependencies/MaxMind/WebService/InvalidRequestException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\IpAddressNotFoundException' => $baseDir . '/src/Dependencies/MaxMind/WebService/IpAddressNotFoundException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\PermissionRequiredException' => $baseDir . '/src/Dependencies/MaxMind/WebService/PermissionRequiredException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\WebServiceException' => $baseDir . '/src/Dependencies/MaxMind/WebService/WebServiceException.php', 'SlimStat\\Dependencies\\Psr\\Cache\\CacheException' => $baseDir . '/src/Dependencies/Psr/Cache/CacheException.php', 'SlimStat\\Dependencies\\Psr\\Cache\\CacheItemInterface' => $baseDir . '/src/Dependencies/Psr/Cache/CacheItemInterface.php', 'SlimStat\\Dependencies\\Psr\\Cache\\CacheItemPoolInterface' => $baseDir . '/src/Dependencies/Psr/Cache/CacheItemPoolInterface.php', @@ -410,15 +731,219 @@ 'SlimStat\\Dependencies\\Symfony\\Polyfill\\Php80\\Php80' => $baseDir . '/src/Dependencies/Symfony/Polyfill/Php80/Php80.php', 'SlimStat\\Exception\\LogException' => $baseDir . '/src/Exception/LogException.php', 'SlimStat\\Helpers\\DataBuckets' => $baseDir . '/src/Helpers/DataBuckets.php', + 'SlimStat\\Interfaces\\RestControllerInterface' => $baseDir . '/src/Interfaces/RestControllerInterface.php', + 'SlimStat\\Migration\\AbstractIndexMigration' => $baseDir . '/src/Migration/AbstractIndexMigration.php', + 'SlimStat\\Migration\\AbstractMigration' => $baseDir . '/src/Migration/AbstractMigration.php', + 'SlimStat\\Migration\\Admin\\MigrationAdmin' => $baseDir . '/src/Migration/Admin/MigrationAdmin.php', + 'SlimStat\\Migration\\MigrationInterface' => $baseDir . '/src/Migration/MigrationInterface.php', + 'SlimStat\\Migration\\MigrationManager' => $baseDir . '/src/Migration/MigrationManager.php', + 'SlimStat\\Migration\\MigrationService' => $baseDir . '/src/Migration/MigrationService.php', + 'SlimStat\\Migration\\Migrations\\CreateCountryDtIndex' => $baseDir . '/src/Migration/Migrations/CreateCountryDtIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtBrowserIndex' => $baseDir . '/src/Migration/Migrations/CreateDtBrowserIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtOutIndex' => $baseDir . '/src/Migration/Migrations/CreateDtOutIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtPlatformIndex' => $baseDir . '/src/Migration/Migrations/CreateDtPlatformIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtScreenIndex' => $baseDir . '/src/Migration/Migrations/CreateDtScreenIndex.php', 'SlimStat\\Modules\\Chart' => $baseDir . '/src/Modules/Chart.php', - 'SlimStat\\Providers\\RESTService' => $baseDir . '/src/Providers/RESTService.php', + 'SlimStat\\Providers\\IPHashProvider' => $baseDir . '/src/Providers/IPHashProvider.php', + 'SlimStat\\Providers\\RestApiManager' => $baseDir . '/src/Providers/RestApiManager.php', + 'SlimStat\\Reports\\Abstracts\\AbstractReport' => $baseDir . '/src/Reports/Abstracts/AbstractReport.php', + 'SlimStat\\Reports\\Abstracts\\ChartReport' => $baseDir . '/src/Reports/Abstracts/ChartReport.php', + 'SlimStat\\Reports\\Abstracts\\SummaryReport' => $baseDir . '/src/Reports/Abstracts/SummaryReport.php', + 'SlimStat\\Reports\\Abstracts\\TableReport' => $baseDir . '/src/Reports/Abstracts/TableReport.php', + 'SlimStat\\Reports\\Bootstrap' => $baseDir . '/src/Reports/Bootstrap.php', + 'SlimStat\\Reports\\Contracts\\ChartableInterface' => $baseDir . '/src/Reports/Contracts/ChartableInterface.php', + 'SlimStat\\Reports\\Contracts\\FilterableInterface' => $baseDir . '/src/Reports/Contracts/FilterableInterface.php', + 'SlimStat\\Reports\\Contracts\\PaginatableInterface' => $baseDir . '/src/Reports/Contracts/PaginatableInterface.php', + 'SlimStat\\Reports\\Contracts\\RenderableInterface' => $baseDir . '/src/Reports/Contracts/RenderableInterface.php', + 'SlimStat\\Reports\\Contracts\\ReportInterface' => $baseDir . '/src/Reports/Contracts/ReportInterface.php', + 'SlimStat\\Reports\\Registry\\LegacyReportAdapter' => $baseDir . '/src/Reports/Registry/LegacyReportAdapter.php', + 'SlimStat\\Reports\\Registry\\ReportFactory' => $baseDir . '/src/Reports/Registry/ReportFactory.php', + 'SlimStat\\Reports\\Registry\\ReportLoader' => $baseDir . '/src/Reports/Registry/ReportLoader.php', + 'SlimStat\\Reports\\Registry\\ReportRegistry' => $baseDir . '/src/Reports/Registry/ReportRegistry.php', + 'SlimStat\\Reports\\Traits\\HasFilters' => $baseDir . '/src/Reports/Traits/HasFilters.php', + 'SlimStat\\Reports\\Traits\\HasPagination' => $baseDir . '/src/Reports/Traits/HasPagination.php', + 'SlimStat\\Reports\\Traits\\HasTooltip' => $baseDir . '/src/Reports/Traits/HasTooltip.php', + 'SlimStat\\Reports\\Types\\Analytics\\LiveAnalyticsReport' => $baseDir . '/src/Reports/Types/Analytics/LiveAnalyticsReport.php', + 'SlimStat\\Services\\Admin\\ConditionTagEvaluator' => $baseDir . '/src/Services/Admin/ConditionTagEvaluator.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationActions' => $baseDir . '/src/Services/Admin/Notification/NotificationActions.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationFactory' => $baseDir . '/src/Services/Admin/Notification/NotificationFactory.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationFetcher' => $baseDir . '/src/Services/Admin/Notification/NotificationFetcher.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationManager' => $baseDir . '/src/Services/Admin/Notification/NotificationManager.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationProcessor' => $baseDir . '/src/Services/Admin/Notification/NotificationProcessor.php', 'SlimStat\\Services\\Browscap' => $baseDir . '/src/Services/Browscap.php', - 'SlimStat\\Services\\GeoIP' => $baseDir . '/src/Services/GeoIP.php', + 'SlimStat\\Services\\Compliance\\ComplianceManager' => $baseDir . '/src/Services/Compliance/ComplianceManager.php', + 'SlimStat\\Services\\Compliance\\Integrations\\ComplianceIntegrationInterface' => $baseDir . '/src/Services/Compliance/Integrations/ComplianceIntegrationInterface.php', + 'SlimStat\\Services\\Compliance\\Regulations\\CCPA\\CCPAServiceProvider' => $baseDir . '/src/Services/Compliance/Regulations/CCPA/CCPAServiceProvider.php', + 'SlimStat\\Services\\Compliance\\Regulations\\LGPD\\LGPDServiceProvider' => $baseDir . '/src/Services/Compliance/Regulations/LGPD/LGPDServiceProvider.php', + 'SlimStat\\Services\\CronEventManager' => $baseDir . '/src/Services/CronEventManager.php', + 'SlimStat\\Services\\GDPRService' => $baseDir . '/src/Services/GDPRService.php', 'SlimStat\\Services\\GeoService' => $baseDir . '/src/Services/GeoService.php', - 'SlimStat\\Utils\\InvalidDatabaseException' => $baseDir . '/src/Utils/InvalidDatabaseException.php', - 'SlimStat\\Utils\\MaxMindDecoder' => $baseDir . '/src/Utils/MaxMindDecoder.php', - 'SlimStat\\Utils\\MaxMindMetadata' => $baseDir . '/src/Utils/MaxMindMetadata.php', - 'SlimStat\\Utils\\MaxMindReader' => $baseDir . '/src/Utils/MaxMindReader.php', - 'SlimStat\\Utils\\MaxMindUtil' => $baseDir . '/src/Utils/MaxMindUtil.php', + 'SlimStat\\Services\\Geolocation\\AbstractGeoIPProvider' => $baseDir . '/src/Services/Geolocation/AbstractGeoIPProvider.php', + 'SlimStat\\Services\\Geolocation\\GeolocationFactory' => $baseDir . '/src/Services/Geolocation/GeolocationFactory.php', + 'SlimStat\\Services\\Geolocation\\GeolocationService' => $baseDir . '/src/Services/Geolocation/GeolocationService.php', + 'SlimStat\\Services\\Geolocation\\Provider\\CloudflareGeolocationProvider' => $baseDir . '/src/Services/Geolocation/Provider/CloudflareGeolocationProvider.php', + 'SlimStat\\Services\\Geolocation\\Provider\\DbIpProvider' => $baseDir . '/src/Services/Geolocation/Provider/DbIpProvider.php', + 'SlimStat\\Services\\Geolocation\\Provider\\GeoServiceProviderInterface' => $baseDir . '/src/Services/Geolocation/Provider/GeoServiceProviderInterface.php', + 'SlimStat\\Services\\Geolocation\\Provider\\MaxmindGeoIPProvider' => $baseDir . '/src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php', + 'SlimStat\\Services\\Privacy' => $baseDir . '/src/Services/Privacy.php', + 'SlimStat\\Services\\Privacy\\ConsentHandler' => $baseDir . '/src/Services/Privacy/ConsentHandler.php', + 'SlimStat\\Services\\Privacy\\DataEraser' => $baseDir . '/src/Services/Privacy/DataEraser.php', + 'SlimStat\\Services\\Privacy\\DataExporter' => $baseDir . '/src/Services/Privacy/DataExporter.php', + 'SlimStat\\Tracker\\Ajax' => $baseDir . '/src/Tracker/Ajax.php', + 'SlimStat\\Tracker\\Processor' => $baseDir . '/src/Tracker/Processor.php', + 'SlimStat\\Tracker\\Routing' => $baseDir . '/src/Tracker/Routing.php', + 'SlimStat\\Tracker\\Session' => $baseDir . '/src/Tracker/Session.php', + 'SlimStat\\Tracker\\Storage' => $baseDir . '/src/Tracker/Storage.php', + 'SlimStat\\Tracker\\Tracker' => $baseDir . '/src/Tracker/Tracker.php', + 'SlimStat\\Tracker\\Utils' => $baseDir . '/src/Tracker/Utils.php', + 'SlimStat\\Utils\\Consent' => $baseDir . '/src/Utils/Consent.php', + 'SlimStat\\Utils\\Query' => $baseDir . '/src/Utils/Query.php', + 'SlimStat\\Utils\\Request' => $baseDir . '/src/Utils/Request.php', 'SlimStat\\Utils\\UADetector' => $baseDir . '/src/Utils/UADetector.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\Attribute\\AsCommand' => $vendorDir . '/symfony/console/Attribute/AsCommand.php', + 'Symfony\\Component\\Console\\CI\\GithubActionReporter' => $vendorDir . '/symfony/console/CI/GithubActionReporter.php', + 'Symfony\\Component\\Console\\Color' => $vendorDir . '/symfony/console/Color.php', + 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => $vendorDir . '/symfony/console/CommandLoader/CommandLoaderInterface.php', + 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/ContainerCommandLoader.php', + 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => $vendorDir . '/symfony/console/CommandLoader/FactoryCommandLoader.php', + 'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\CompleteCommand' => $vendorDir . '/symfony/console/Command/CompleteCommand.php', + 'Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => $vendorDir . '/symfony/console/Command/DumpCompletionCommand.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\LazyCommand' => $vendorDir . '/symfony/console/Command/LazyCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\Command\\LockableTrait' => $vendorDir . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => $vendorDir . '/symfony/console/Command/SignalableCommandInterface.php', + 'Symfony\\Component\\Console\\Completion\\CompletionInput' => $vendorDir . '/symfony/console/Completion/CompletionInput.php', + 'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => $vendorDir . '/symfony/console/Completion/CompletionSuggestions.php', + 'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/BashCompletionOutput.php', + 'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => $vendorDir . '/symfony/console/Completion/Output/CompletionOutputInterface.php', + 'Symfony\\Component\\Console\\Completion\\Suggestion' => $vendorDir . '/symfony/console/Completion/Suggestion.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Cursor' => $vendorDir . '/symfony/console/Cursor.php', + 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => $vendorDir . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => $vendorDir . '/symfony/console/Event/ConsoleErrorEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => $vendorDir . '/symfony/console/Event/ConsoleSignalEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\MissingInputException' => $vendorDir . '/symfony/console/Exception/MissingInputException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => $vendorDir . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/NullOutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => $vendorDir . '/symfony/console/Helper/Dumper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableCellStyle' => $vendorDir . '/symfony/console/Helper/TableCellStyle.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => $vendorDir . '/symfony/console/Helper/TableRows.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => $vendorDir . '/symfony/console/Output/ConsoleSectionOutput.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => $vendorDir . '/symfony/console/Output/TrimmedBufferOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => $vendorDir . '/symfony/console/SignalRegistry/SignalRegistry.php', + 'Symfony\\Component\\Console\\SingleCommandApplication' => $vendorDir . '/symfony/console/SingleCommandApplication.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Terminal' => $vendorDir . '/symfony/console/Terminal.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => $vendorDir . '/symfony/console/Tester/CommandCompletionTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => $vendorDir . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => $vendorDir . '/symfony/console/Tester/TesterTrait.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => $vendorDir . '/symfony/filesystem/Exception/RuntimeException.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Filesystem\\Path' => $vendorDir . '/symfony/filesystem/Path.php', + 'Symfony\\Component\\String\\AbstractString' => $vendorDir . '/symfony/string/AbstractString.php', + 'Symfony\\Component\\String\\AbstractUnicodeString' => $vendorDir . '/symfony/string/AbstractUnicodeString.php', + 'Symfony\\Component\\String\\ByteString' => $vendorDir . '/symfony/string/ByteString.php', + 'Symfony\\Component\\String\\CodePointString' => $vendorDir . '/symfony/string/CodePointString.php', + 'Symfony\\Component\\String\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/string/Exception/ExceptionInterface.php', + 'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/string/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\String\\Exception\\RuntimeException' => $vendorDir . '/symfony/string/Exception/RuntimeException.php', + 'Symfony\\Component\\String\\Inflector\\EnglishInflector' => $vendorDir . '/symfony/string/Inflector/EnglishInflector.php', + 'Symfony\\Component\\String\\Inflector\\FrenchInflector' => $vendorDir . '/symfony/string/Inflector/FrenchInflector.php', + 'Symfony\\Component\\String\\Inflector\\InflectorInterface' => $vendorDir . '/symfony/string/Inflector/InflectorInterface.php', + 'Symfony\\Component\\String\\LazyString' => $vendorDir . '/symfony/string/LazyString.php', + 'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => $vendorDir . '/symfony/string/Slugger/AsciiSlugger.php', + 'Symfony\\Component\\String\\Slugger\\SluggerInterface' => $vendorDir . '/symfony/string/Slugger/SluggerInterface.php', + 'Symfony\\Component\\String\\UnicodeString' => $vendorDir . '/symfony/string/UnicodeString.php', + 'Symfony\\Contracts\\Service\\Attribute\\Required' => $vendorDir . '/symfony/service-contracts/Attribute/Required.php', + 'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => $vendorDir . '/symfony/service-contracts/Attribute/SubscribedService.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceCollectionInterface' => $vendorDir . '/symfony/service-contracts/ServiceCollectionInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceMethodsSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceMethodsSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => $vendorDir . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => $vendorDir . '/symfony/polyfill-intl-grapheme/Grapheme.php', + 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php', + 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => $vendorDir . '/symfony/polyfill-php80/PhpToken.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); @@ -6,6 +6,14 @@ $baseDir = dirname($vendorDir); return array( - '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', ); @@ -6,5 +6,28 @@ $baseDir = dirname($vendorDir); return array( + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'), + 'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), 'SlimStat\\' => array($baseDir . '/src'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), + 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'MatthiasMullie\\Scrapbook\\' => array($vendorDir . '/matthiasmullie/scrapbook/src'), + 'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), + 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), + 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'), + 'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'BrowscapPHP\\' => array($vendorDir . '/veronalabs/browscap-php/src'), ); @@ -7,27 +7,439 @@ class ComposerStaticInit09c8e0e0cfea3b2fb368cb99a2eeaaec { public static $files = array ( - '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php', + 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', + '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php', + 'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php', + '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', ); public static $prefixLengthsPsr4 = array ( 'S' => array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php73\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33, + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Contracts\\Service\\' => 26, + 'Symfony\\Component\\String\\' => 25, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\Console\\' => 26, 'SlimStat\\' => 9, ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Http\\Message\\' => 17, + 'Psr\\Http\\Client\\' => 16, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + ), + 'M' => + array ( + 'MatthiasMullie\\Scrapbook\\' => 25, + ), + 'L' => + array ( + 'League\\MimeTypeDetection\\' => 25, + 'League\\Flysystem\\' => 17, + ), + 'G' => + array ( + 'GuzzleHttp\\Psr7\\' => 16, + 'GuzzleHttp\\Promise\\' => 19, + 'GuzzleHttp\\' => 11, + ), + 'B' => + array ( + 'BrowscapPHP\\' => 12, + ), ); public static $prefixDirsPsr4 = array ( + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php73\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php73', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Intl\\Normalizer\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer', + ), + 'Symfony\\Polyfill\\Intl\\Grapheme\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Contracts\\Service\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/service-contracts', + ), + 'Symfony\\Component\\String\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/string', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), 'SlimStat\\' => array ( 0 => __DIR__ . '/../..' . '/src', ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Http\\Message\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', + ), + 'Psr\\Http\\Client\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/http-client/src', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'MatthiasMullie\\Scrapbook\\' => + array ( + 0 => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src', + ), + 'League\\MimeTypeDetection\\' => + array ( + 0 => __DIR__ . '/..' . '/league/mime-type-detection/src', + ), + 'League\\Flysystem\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem/src', + ), + 'GuzzleHttp\\Psr7\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src', + ), + 'GuzzleHttp\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/promises/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'BrowscapPHP\\' => + array ( + 0 => __DIR__ . '/..' . '/veronalabs/browscap-php/src', + ), ); public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'BrowscapPHP\\Browscap' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Browscap.php', + 'BrowscapPHP\\BrowscapInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/BrowscapInterface.php', + 'BrowscapPHP\\BrowscapUpdater' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/BrowscapUpdater.php', + 'BrowscapPHP\\BrowscapUpdaterInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/BrowscapUpdaterInterface.php', + 'BrowscapPHP\\Cache\\BrowscapCache' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Cache/BrowscapCache.php', + 'BrowscapPHP\\Cache\\BrowscapCacheInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Cache/BrowscapCacheInterface.php', + 'BrowscapPHP\\Command\\CheckUpdateCommand' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Command/CheckUpdateCommand.php', + 'BrowscapPHP\\Command\\ConvertCommand' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Command/ConvertCommand.php', + 'BrowscapPHP\\Command\\FetchCommand' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Command/FetchCommand.php', + 'BrowscapPHP\\Command\\ParserCommand' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Command/ParserCommand.php', + 'BrowscapPHP\\Command\\UpdateCommand' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Command/UpdateCommand.php', + 'BrowscapPHP\\Data\\PropertyFormatter' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Data/PropertyFormatter.php', + 'BrowscapPHP\\Data\\PropertyHolder' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Data/PropertyHolder.php', + 'BrowscapPHP\\Exception' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception.php', + 'BrowscapPHP\\Exception\\DomainException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/DomainException.php', + 'BrowscapPHP\\Exception\\ErrorCachedVersionException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/ErrorCachedVersionException.php', + 'BrowscapPHP\\Exception\\ErrorReadingFileException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/ErrorReadingFileException.php', + 'BrowscapPHP\\Exception\\FetcherException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/FetcherException.php', + 'BrowscapPHP\\Exception\\FileNameMissingException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/FileNameMissingException.php', + 'BrowscapPHP\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/FileNotFoundException.php', + 'BrowscapPHP\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/InvalidArgumentException.php', + 'BrowscapPHP\\Exception\\NoCachedVersionException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/NoCachedVersionException.php', + 'BrowscapPHP\\Exception\\NoNewVersionException' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Exception/NoNewVersionException.php', + 'BrowscapPHP\\Formatter\\FormatterInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Formatter/FormatterInterface.php', + 'BrowscapPHP\\Formatter\\LegacyFormatter' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Formatter/LegacyFormatter.php', + 'BrowscapPHP\\Formatter\\PhpGetBrowser' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Formatter/PhpGetBrowser.php', + 'BrowscapPHP\\Helper\\Converter' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/Converter.php', + 'BrowscapPHP\\Helper\\ConverterInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/ConverterInterface.php', + 'BrowscapPHP\\Helper\\Exception' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/Exception.php', + 'BrowscapPHP\\Helper\\Filesystem' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/Filesystem.php', + 'BrowscapPHP\\Helper\\IniLoader' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/IniLoader.php', + 'BrowscapPHP\\Helper\\IniLoaderInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/IniLoaderInterface.php', + 'BrowscapPHP\\Helper\\LoggerHelper' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/LoggerHelper.php', + 'BrowscapPHP\\Helper\\Quoter' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/Quoter.php', + 'BrowscapPHP\\Helper\\QuoterInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/QuoterInterface.php', + 'BrowscapPHP\\Helper\\Support' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/Support.php', + 'BrowscapPHP\\Helper\\SupportInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Helper/SupportInterface.php', + 'BrowscapPHP\\IniParser\\IniParser' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/IniParser/IniParser.php', + 'BrowscapPHP\\IniParser\\ParserInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/IniParser/ParserInterface.php', + 'BrowscapPHP\\Parser\\Helper\\GetData' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/Helper/GetData.php', + 'BrowscapPHP\\Parser\\Helper\\GetDataInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/Helper/GetDataInterface.php', + 'BrowscapPHP\\Parser\\Helper\\GetPattern' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/Helper/GetPattern.php', + 'BrowscapPHP\\Parser\\Helper\\GetPatternInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/Helper/GetPatternInterface.php', + 'BrowscapPHP\\Parser\\Helper\\Pattern' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/Helper/Pattern.php', + 'BrowscapPHP\\Parser\\Helper\\SubKey' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/Helper/SubKey.php', + 'BrowscapPHP\\Parser\\Ini' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/Ini.php', + 'BrowscapPHP\\Parser\\ParserInterface' => __DIR__ . '/..' . '/veronalabs/browscap-php/src/Parser/ParserInterface.php', 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'GuzzleHttp\\BodySummarizer' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/BodySummarizer.php', + 'GuzzleHttp\\BodySummarizerInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/BodySummarizerInterface.php', + 'GuzzleHttp\\Client' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\ClientTrait' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientTrait.php', + 'GuzzleHttp\\Cookie\\CookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Exception\\BadResponseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\GuzzleException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/GuzzleException.php', + 'GuzzleHttp\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/InvalidArgumentException.php', + 'GuzzleHttp\\Exception\\RequestException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\ServerException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\HandlerStack' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/HandlerStack.php', + 'GuzzleHttp\\Handler\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactory.php', + 'GuzzleHttp\\Handler\\CurlFactoryInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlFactoryInterface.php', + 'GuzzleHttp\\Handler\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlHandler.php', + 'GuzzleHttp\\Handler\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php', + 'GuzzleHttp\\Handler\\EasyHandle' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/EasyHandle.php', + 'GuzzleHttp\\Handler\\HeaderProcessor' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/HeaderProcessor.php', + 'GuzzleHttp\\Handler\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/MockHandler.php', + 'GuzzleHttp\\Handler\\Proxy' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/Proxy.php', + 'GuzzleHttp\\Handler\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Handler/StreamHandler.php', + 'GuzzleHttp\\MessageFormatter' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatter.php', + 'GuzzleHttp\\MessageFormatterInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/MessageFormatterInterface.php', + 'GuzzleHttp\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Middleware.php', + 'GuzzleHttp\\Pool' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\PrepareBodyMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/PrepareBodyMiddleware.php', + 'GuzzleHttp\\Promise\\AggregateException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/AggregateException.php', + 'GuzzleHttp\\Promise\\CancellationException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/CancellationException.php', + 'GuzzleHttp\\Promise\\Coroutine' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Coroutine.php', + 'GuzzleHttp\\Promise\\Create' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Create.php', + 'GuzzleHttp\\Promise\\Each' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Each.php', + 'GuzzleHttp\\Promise\\EachPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/EachPromise.php', + 'GuzzleHttp\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/FulfilledPromise.php', + 'GuzzleHttp\\Promise\\Is' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Is.php', + 'GuzzleHttp\\Promise\\Promise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Promise.php', + 'GuzzleHttp\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromiseInterface.php', + 'GuzzleHttp\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/PromisorInterface.php', + 'GuzzleHttp\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectedPromise.php', + 'GuzzleHttp\\Promise\\RejectionException' => __DIR__ . '/..' . '/guzzlehttp/promises/src/RejectionException.php', + 'GuzzleHttp\\Promise\\TaskQueue' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueue.php', + 'GuzzleHttp\\Promise\\TaskQueueInterface' => __DIR__ . '/..' . '/guzzlehttp/promises/src/TaskQueueInterface.php', + 'GuzzleHttp\\Promise\\Utils' => __DIR__ . '/..' . '/guzzlehttp/promises/src/Utils.php', + 'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php', + 'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php', + 'GuzzleHttp\\Psr7\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/CachingStream.php', + 'GuzzleHttp\\Psr7\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/DroppingStream.php', + 'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php', + 'GuzzleHttp\\Psr7\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/FnStream.php', + 'GuzzleHttp\\Psr7\\Header' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Header.php', + 'GuzzleHttp\\Psr7\\HttpFactory' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/HttpFactory.php', + 'GuzzleHttp\\Psr7\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/InflateStream.php', + 'GuzzleHttp\\Psr7\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LazyOpenStream.php', + 'GuzzleHttp\\Psr7\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LimitStream.php', + 'GuzzleHttp\\Psr7\\Message' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Message.php', + 'GuzzleHttp\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MessageTrait.php', + 'GuzzleHttp\\Psr7\\MimeType' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MimeType.php', + 'GuzzleHttp\\Psr7\\MultipartStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MultipartStream.php', + 'GuzzleHttp\\Psr7\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/NoSeekStream.php', + 'GuzzleHttp\\Psr7\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/PumpStream.php', + 'GuzzleHttp\\Psr7\\Query' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Query.php', + 'GuzzleHttp\\Psr7\\Request' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Request.php', + 'GuzzleHttp\\Psr7\\Response' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Response.php', + 'GuzzleHttp\\Psr7\\Rfc7230' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Rfc7230.php', + 'GuzzleHttp\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/ServerRequest.php', + 'GuzzleHttp\\Psr7\\Stream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Stream.php', + 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Psr7\\StreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamWrapper.php', + 'GuzzleHttp\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UploadedFile.php', + 'GuzzleHttp\\Psr7\\Uri' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Uri.php', + 'GuzzleHttp\\Psr7\\UriComparator' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriComparator.php', + 'GuzzleHttp\\Psr7\\UriNormalizer' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriNormalizer.php', + 'GuzzleHttp\\Psr7\\UriResolver' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriResolver.php', + 'GuzzleHttp\\Psr7\\Utils' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Utils.php', + 'GuzzleHttp\\RedirectMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RedirectMiddleware.php', + 'GuzzleHttp\\RequestOptions' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RequestOptions.php', + 'GuzzleHttp\\RetryMiddleware' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RetryMiddleware.php', + 'GuzzleHttp\\TransferStats' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/TransferStats.php', + 'GuzzleHttp\\Utils' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Utils.php', + 'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php', + 'League\\Flysystem\\Config' => __DIR__ . '/..' . '/league/flysystem/src/Config.php', + 'League\\Flysystem\\CorruptedPathDetected' => __DIR__ . '/..' . '/league/flysystem/src/CorruptedPathDetected.php', + 'League\\Flysystem\\DirectoryAttributes' => __DIR__ . '/..' . '/league/flysystem/src/DirectoryAttributes.php', + 'League\\Flysystem\\DirectoryListing' => __DIR__ . '/..' . '/league/flysystem/src/DirectoryListing.php', + 'League\\Flysystem\\FileAttributes' => __DIR__ . '/..' . '/league/flysystem/src/FileAttributes.php', + 'League\\Flysystem\\Filesystem' => __DIR__ . '/..' . '/league/flysystem/src/Filesystem.php', + 'League\\Flysystem\\FilesystemAdapter' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemAdapter.php', + 'League\\Flysystem\\FilesystemException' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemException.php', + 'League\\Flysystem\\FilesystemOperationFailed' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemOperationFailed.php', + 'League\\Flysystem\\FilesystemOperator' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemOperator.php', + 'League\\Flysystem\\FilesystemReader' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemReader.php', + 'League\\Flysystem\\FilesystemWriter' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemWriter.php', + 'League\\Flysystem\\InvalidStreamProvided' => __DIR__ . '/..' . '/league/flysystem/src/InvalidStreamProvided.php', + 'League\\Flysystem\\InvalidVisibilityProvided' => __DIR__ . '/..' . '/league/flysystem/src/InvalidVisibilityProvided.php', + 'League\\Flysystem\\Local\\LocalFilesystemAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Local/LocalFilesystemAdapter.php', + 'League\\Flysystem\\MountManager' => __DIR__ . '/..' . '/league/flysystem/src/MountManager.php', + 'League\\Flysystem\\PathNormalizer' => __DIR__ . '/..' . '/league/flysystem/src/PathNormalizer.php', + 'League\\Flysystem\\PathPrefixer' => __DIR__ . '/..' . '/league/flysystem/src/PathPrefixer.php', + 'League\\Flysystem\\PathTraversalDetected' => __DIR__ . '/..' . '/league/flysystem/src/PathTraversalDetected.php', + 'League\\Flysystem\\PortableVisibilityGuard' => __DIR__ . '/..' . '/league/flysystem/src/PortableVisibilityGuard.php', + 'League\\Flysystem\\ProxyArrayAccessToProperties' => __DIR__ . '/..' . '/league/flysystem/src/ProxyArrayAccessToProperties.php', + 'League\\Flysystem\\StorageAttributes' => __DIR__ . '/..' . '/league/flysystem/src/StorageAttributes.php', + 'League\\Flysystem\\SymbolicLinkEncountered' => __DIR__ . '/..' . '/league/flysystem/src/SymbolicLinkEncountered.php', + 'League\\Flysystem\\UnableToCheckFileExistence' => __DIR__ . '/..' . '/league/flysystem/src/UnableToCheckFileExistence.php', + 'League\\Flysystem\\UnableToCopyFile' => __DIR__ . '/..' . '/league/flysystem/src/UnableToCopyFile.php', + 'League\\Flysystem\\UnableToCreateDirectory' => __DIR__ . '/..' . '/league/flysystem/src/UnableToCreateDirectory.php', + 'League\\Flysystem\\UnableToDeleteDirectory' => __DIR__ . '/..' . '/league/flysystem/src/UnableToDeleteDirectory.php', + 'League\\Flysystem\\UnableToDeleteFile' => __DIR__ . '/..' . '/league/flysystem/src/UnableToDeleteFile.php', + 'League\\Flysystem\\UnableToMountFilesystem' => __DIR__ . '/..' . '/league/flysystem/src/UnableToMountFilesystem.php', + 'League\\Flysystem\\UnableToMoveFile' => __DIR__ . '/..' . '/league/flysystem/src/UnableToMoveFile.php', + 'League\\Flysystem\\UnableToReadFile' => __DIR__ . '/..' . '/league/flysystem/src/UnableToReadFile.php', + 'League\\Flysystem\\UnableToResolveFilesystemMount' => __DIR__ . '/..' . '/league/flysystem/src/UnableToResolveFilesystemMount.php', + 'League\\Flysystem\\UnableToRetrieveMetadata' => __DIR__ . '/..' . '/league/flysystem/src/UnableToRetrieveMetadata.php', + 'League\\Flysystem\\UnableToSetVisibility' => __DIR__ . '/..' . '/league/flysystem/src/UnableToSetVisibility.php', + 'League\\Flysystem\\UnableToWriteFile' => __DIR__ . '/..' . '/league/flysystem/src/UnableToWriteFile.php', + 'League\\Flysystem\\UnixVisibility\\PortableVisibilityConverter' => __DIR__ . '/..' . '/league/flysystem/src/UnixVisibility/PortableVisibilityConverter.php', + 'League\\Flysystem\\UnixVisibility\\VisibilityConverter' => __DIR__ . '/..' . '/league/flysystem/src/UnixVisibility/VisibilityConverter.php', + 'League\\Flysystem\\UnreadableFileEncountered' => __DIR__ . '/..' . '/league/flysystem/src/UnreadableFileEncountered.php', + 'League\\Flysystem\\Visibility' => __DIR__ . '/..' . '/league/flysystem/src/Visibility.php', + 'League\\Flysystem\\WhitespacePathNormalizer' => __DIR__ . '/..' . '/league/flysystem/src/WhitespacePathNormalizer.php', + 'League\\MimeTypeDetection\\EmptyExtensionToMimeTypeMap' => __DIR__ . '/..' . '/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\ExtensionLookup' => __DIR__ . '/..' . '/league/mime-type-detection/src/ExtensionLookup.php', + 'League\\MimeTypeDetection\\ExtensionMimeTypeDetector' => __DIR__ . '/..' . '/league/mime-type-detection/src/ExtensionMimeTypeDetector.php', + 'League\\MimeTypeDetection\\ExtensionToMimeTypeMap' => __DIR__ . '/..' . '/league/mime-type-detection/src/ExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\FinfoMimeTypeDetector' => __DIR__ . '/..' . '/league/mime-type-detection/src/FinfoMimeTypeDetector.php', + 'League\\MimeTypeDetection\\GeneratedExtensionToMimeTypeMap' => __DIR__ . '/..' . '/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\MimeTypeDetector' => __DIR__ . '/..' . '/league/mime-type-detection/src/MimeTypeDetector.php', + 'League\\MimeTypeDetection\\OverridingExtensionToMimeTypeMap' => __DIR__ . '/..' . '/league/mime-type-detection/src/OverridingExtensionToMimeTypeMap.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Apc' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Apc.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Apc' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/Apc.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Couchbase' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/Couchbase.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Flysystem' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/Flysystem.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Memcached' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/Memcached.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\MemoryStore' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/MemoryStore.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Redis' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/Redis.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\SQL' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/SQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Utils\\PrefixKeys' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixKeys.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Collections\\Utils\\PrefixReset' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Collections/Utils/PrefixReset.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Couchbase' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Couchbase.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Flysystem' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Flysystem.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Memcached' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Memcached.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\MemoryStore' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/MemoryStore.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\MySQL' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/MySQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\PostgreSQL' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/PostgreSQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\Redis' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/Redis.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\SQL' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/SQL.php', + 'MatthiasMullie\\Scrapbook\\Adapters\\SQLite' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Adapters/SQLite.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\BufferedStore' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Buffered/BufferedStore.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\TransactionalStore' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Buffered/TransactionalStore.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\Buffer' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Buffered/Utils/Buffer.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\BufferCollection' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Buffered/Utils/BufferCollection.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\Defer' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Buffered/Utils/Defer.php', + 'MatthiasMullie\\Scrapbook\\Buffered\\Utils\\Transaction' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Buffered/Utils/Transaction.php', + 'MatthiasMullie\\Scrapbook\\Exception\\Exception' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Exception/Exception.php', + 'MatthiasMullie\\Scrapbook\\Exception\\InvalidCollection' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Exception/InvalidCollection.php', + 'MatthiasMullie\\Scrapbook\\Exception\\InvalidKey' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Exception/InvalidKey.php', + 'MatthiasMullie\\Scrapbook\\Exception\\OperationFailed' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Exception/OperationFailed.php', + 'MatthiasMullie\\Scrapbook\\Exception\\ServerUnhealthy' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Exception/ServerUnhealthy.php', + 'MatthiasMullie\\Scrapbook\\Exception\\UnbegunTransaction' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Exception/UnbegunTransaction.php', + 'MatthiasMullie\\Scrapbook\\Exception\\UncommittedTransaction' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Exception/UncommittedTransaction.php', + 'MatthiasMullie\\Scrapbook\\KeyValueStore' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/KeyValueStore.php', + 'MatthiasMullie\\Scrapbook\\Psr16\\InvalidArgumentException' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Psr16/InvalidArgumentException.php', + 'MatthiasMullie\\Scrapbook\\Psr16\\SimpleCache' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Psr16/SimpleCache.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\InvalidArgumentException' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Psr6/InvalidArgumentException.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\Item' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Psr6/Item.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\Pool' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Psr6/Pool.php', + 'MatthiasMullie\\Scrapbook\\Psr6\\Repository' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Psr6/Repository.php', + 'MatthiasMullie\\Scrapbook\\Scale\\Shard' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Scale/Shard.php', + 'MatthiasMullie\\Scrapbook\\Scale\\StampedeProtector' => __DIR__ . '/..' . '/matthiasmullie/scrapbook/src/Scale/StampedeProtector.php', + 'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Http\\Client\\ClientExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientExceptionInterface.php', + 'Psr\\Http\\Client\\ClientInterface' => __DIR__ . '/..' . '/psr/http-client/src/ClientInterface.php', + 'Psr\\Http\\Client\\NetworkExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/NetworkExceptionInterface.php', + 'Psr\\Http\\Client\\RequestExceptionInterface' => __DIR__ . '/..' . '/psr/http-client/src/RequestExceptionInterface.php', + 'Psr\\Http\\Message\\MessageInterface' => __DIR__ . '/..' . '/psr/http-message/src/MessageInterface.php', + 'Psr\\Http\\Message\\RequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/RequestFactoryInterface.php', + 'Psr\\Http\\Message\\RequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/RequestInterface.php', + 'Psr\\Http\\Message\\ResponseFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ResponseFactoryInterface.php', + 'Psr\\Http\\Message\\ResponseInterface' => __DIR__ . '/..' . '/psr/http-message/src/ResponseInterface.php', + 'Psr\\Http\\Message\\ServerRequestFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/ServerRequestFactoryInterface.php', + 'Psr\\Http\\Message\\ServerRequestInterface' => __DIR__ . '/..' . '/psr/http-message/src/ServerRequestInterface.php', + 'Psr\\Http\\Message\\StreamFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/StreamFactoryInterface.php', + 'Psr\\Http\\Message\\StreamInterface' => __DIR__ . '/..' . '/psr/http-message/src/StreamInterface.php', + 'Psr\\Http\\Message\\UploadedFileFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UploadedFileFactoryInterface.php', + 'Psr\\Http\\Message\\UploadedFileInterface' => __DIR__ . '/..' . '/psr/http-message/src/UploadedFileInterface.php', + 'Psr\\Http\\Message\\UriFactoryInterface' => __DIR__ . '/..' . '/psr/http-factory/src/UriFactoryInterface.php', + 'Psr\\Http\\Message\\UriInterface' => __DIR__ . '/..' . '/psr/http-message/src/UriInterface.php', + 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/DummyTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php', + 'Psr\\SimpleCache\\CacheException' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/simple-cache/src/InvalidArgumentException.php', + 'SlimStat\\Components\\Ajax' => __DIR__ . '/../..' . '/src/Components/Ajax.php', + 'SlimStat\\Components\\DateRangeHelper' => __DIR__ . '/../..' . '/src/Components/DateRangeHelper.php', + 'SlimStat\\Components\\Event' => __DIR__ . '/../..' . '/src/Components/Event.php', + 'SlimStat\\Components\\RemoteRequest' => __DIR__ . '/../..' . '/src/Components/RemoteRequest.php', 'SlimStat\\Components\\View' => __DIR__ . '/../..' . '/src/Components/View.php', + 'SlimStat\\Controllers\\Rest\\ConsentChangeRestController' => __DIR__ . '/../..' . '/src/Controllers/Rest/ConsentChangeRestController.php', + 'SlimStat\\Controllers\\Rest\\ConsentHealthRestController' => __DIR__ . '/../..' . '/src/Controllers/Rest/ConsentHealthRestController.php', + 'SlimStat\\Controllers\\Rest\\GDPRBannerRestController' => __DIR__ . '/../..' . '/src/Controllers/Rest/GDPRBannerRestController.php', + 'SlimStat\\Controllers\\Rest\\TrackingRestController' => __DIR__ . '/../..' . '/src/Controllers/Rest/TrackingRestController.php', + 'SlimStat\\Decorators\\NotificationDecorator' => __DIR__ . '/../..' . '/src/Decorators/NotificationDecorator.php', 'SlimStat\\Dependencies\\BrowscapPHP\\Browscap' => __DIR__ . '/../..' . '/src/Dependencies/BrowscapPHP/Browscap.php', 'SlimStat\\Dependencies\\BrowscapPHP\\BrowscapInterface' => __DIR__ . '/../..' . '/src/Dependencies/BrowscapPHP/BrowscapInterface.php', 'SlimStat\\Dependencies\\BrowscapPHP\\BrowscapUpdater' => __DIR__ . '/../..' . '/src/Dependencies/BrowscapPHP/BrowscapUpdater.php', @@ -75,6 +487,37 @@ 'SlimStat\\Dependencies\\BrowscapPHP\\Parser\\Helper\\SubKey' => __DIR__ . '/../..' . '/src/Dependencies/BrowscapPHP/Parser/Helper/SubKey.php', 'SlimStat\\Dependencies\\BrowscapPHP\\Parser\\Ini' => __DIR__ . '/../..' . '/src/Dependencies/BrowscapPHP/Parser/Ini.php', 'SlimStat\\Dependencies\\BrowscapPHP\\Parser\\ParserInterface' => __DIR__ . '/../..' . '/src/Dependencies/BrowscapPHP/Parser/ParserInterface.php', + 'SlimStat\\Dependencies\\GeoIp2\\Database\\Reader' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Database/Reader.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\AddressNotFoundException' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Exception/AddressNotFoundException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\AuthenticationException' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Exception/AuthenticationException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\GeoIp2Exception' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Exception/GeoIp2Exception.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\HttpException' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Exception/HttpException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\InvalidRequestException' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Exception/InvalidRequestException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Exception\\OutOfQueriesException' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Exception/OutOfQueriesException.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\AbstractModel' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/AbstractModel.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\AnonymousIp' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/AnonymousIp.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Asn' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/Asn.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\City' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/City.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\ConnectionType' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/ConnectionType.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Country' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/Country.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Domain' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/Domain.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Enterprise' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/Enterprise.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Insights' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/Insights.php', + 'SlimStat\\Dependencies\\GeoIp2\\Model\\Isp' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Model/Isp.php', + 'SlimStat\\Dependencies\\GeoIp2\\ProviderInterface' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/ProviderInterface.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\AbstractPlaceRecord' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/AbstractPlaceRecord.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\AbstractRecord' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/AbstractRecord.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\City' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/City.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Continent' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/Continent.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Country' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/Country.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Location' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/Location.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\MaxMind' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/MaxMind.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Postal' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/Postal.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\RepresentedCountry' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/RepresentedCountry.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Subdivision' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/Subdivision.php', + 'SlimStat\\Dependencies\\GeoIp2\\Record\\Traits' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Record/Traits.php', + 'SlimStat\\Dependencies\\GeoIp2\\Util' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/Util.php', + 'SlimStat\\Dependencies\\GeoIp2\\WebService\\Client' => __DIR__ . '/../..' . '/src/Dependencies/GeoIp2/WebService/Client.php', 'SlimStat\\Dependencies\\GuzzleHttp\\BodySummarizer' => __DIR__ . '/../..' . '/src/Dependencies/GuzzleHttp/BodySummarizer.php', 'SlimStat\\Dependencies\\GuzzleHttp\\BodySummarizerInterface' => __DIR__ . '/../..' . '/src/Dependencies/GuzzleHttp/BodySummarizerInterface.php', 'SlimStat\\Dependencies\\GuzzleHttp\\Client' => __DIR__ . '/../..' . '/src/Dependencies/GuzzleHttp/Client.php', @@ -250,6 +693,23 @@ 'SlimStat\\Dependencies\\MatthiasMullie\\Scrapbook\\Psr6\\Repository' => __DIR__ . '/../..' . '/src/Dependencies/MatthiasMullie/Scrapbook/Psr6/Repository.php', 'SlimStat\\Dependencies\\MatthiasMullie\\Scrapbook\\Scale\\Shard' => __DIR__ . '/../..' . '/src/Dependencies/MatthiasMullie/Scrapbook/Scale/Shard.php', 'SlimStat\\Dependencies\\MatthiasMullie\\Scrapbook\\Scale\\StampedeProtector' => __DIR__ . '/../..' . '/src/Dependencies/MatthiasMullie/Scrapbook/Scale/StampedeProtector.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/Db/Reader.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\Decoder' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/Db/Reader/Decoder.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\InvalidDatabaseException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/Db/Reader/InvalidDatabaseException.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\Metadata' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/Db/Reader/Metadata.php', + 'SlimStat\\Dependencies\\MaxMind\\Db\\Reader\\Util' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/Db/Reader/Util.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\AuthenticationException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/AuthenticationException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Client' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/Client.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\HttpException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/HttpException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Http\\CurlRequest' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/Http/CurlRequest.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Http\\Request' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/Http/Request.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\Http\\RequestFactory' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/Http/RequestFactory.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\InsufficientFundsException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/InsufficientFundsException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\InvalidInputException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/InvalidInputException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\InvalidRequestException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/InvalidRequestException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\IpAddressNotFoundException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/IpAddressNotFoundException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\PermissionRequiredException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/PermissionRequiredException.php', + 'SlimStat\\Dependencies\\MaxMind\\WebService\\WebServiceException' => __DIR__ . '/../..' . '/src/Dependencies/MaxMind/WebService/WebServiceException.php', 'SlimStat\\Dependencies\\Psr\\Cache\\CacheException' => __DIR__ . '/../..' . '/src/Dependencies/Psr/Cache/CacheException.php', 'SlimStat\\Dependencies\\Psr\\Cache\\CacheItemInterface' => __DIR__ . '/../..' . '/src/Dependencies/Psr/Cache/CacheItemInterface.php', 'SlimStat\\Dependencies\\Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/../..' . '/src/Dependencies/Psr/Cache/CacheItemPoolInterface.php', @@ -430,17 +890,221 @@ 'SlimStat\\Dependencies\\Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/../..' . '/src/Dependencies/Symfony/Polyfill/Php80/Php80.php', 'SlimStat\\Exception\\LogException' => __DIR__ . '/../..' . '/src/Exception/LogException.php', 'SlimStat\\Helpers\\DataBuckets' => __DIR__ . '/../..' . '/src/Helpers/DataBuckets.php', + 'SlimStat\\Interfaces\\RestControllerInterface' => __DIR__ . '/../..' . '/src/Interfaces/RestControllerInterface.php', + 'SlimStat\\Migration\\AbstractIndexMigration' => __DIR__ . '/../..' . '/src/Migration/AbstractIndexMigration.php', + 'SlimStat\\Migration\\AbstractMigration' => __DIR__ . '/../..' . '/src/Migration/AbstractMigration.php', + 'SlimStat\\Migration\\Admin\\MigrationAdmin' => __DIR__ . '/../..' . '/src/Migration/Admin/MigrationAdmin.php', + 'SlimStat\\Migration\\MigrationInterface' => __DIR__ . '/../..' . '/src/Migration/MigrationInterface.php', + 'SlimStat\\Migration\\MigrationManager' => __DIR__ . '/../..' . '/src/Migration/MigrationManager.php', + 'SlimStat\\Migration\\MigrationService' => __DIR__ . '/../..' . '/src/Migration/MigrationService.php', + 'SlimStat\\Migration\\Migrations\\CreateCountryDtIndex' => __DIR__ . '/../..' . '/src/Migration/Migrations/CreateCountryDtIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtBrowserIndex' => __DIR__ . '/../..' . '/src/Migration/Migrations/CreateDtBrowserIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtOutIndex' => __DIR__ . '/../..' . '/src/Migration/Migrations/CreateDtOutIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtPlatformIndex' => __DIR__ . '/../..' . '/src/Migration/Migrations/CreateDtPlatformIndex.php', + 'SlimStat\\Migration\\Migrations\\CreateDtScreenIndex' => __DIR__ . '/../..' . '/src/Migration/Migrations/CreateDtScreenIndex.php', 'SlimStat\\Modules\\Chart' => __DIR__ . '/../..' . '/src/Modules/Chart.php', - 'SlimStat\\Providers\\RESTService' => __DIR__ . '/../..' . '/src/Providers/RESTService.php', + 'SlimStat\\Providers\\IPHashProvider' => __DIR__ . '/../..' . '/src/Providers/IPHashProvider.php', + 'SlimStat\\Providers\\RestApiManager' => __DIR__ . '/../..' . '/src/Providers/RestApiManager.php', + 'SlimStat\\Reports\\Abstracts\\AbstractReport' => __DIR__ . '/../..' . '/src/Reports/Abstracts/AbstractReport.php', + 'SlimStat\\Reports\\Abstracts\\ChartReport' => __DIR__ . '/../..' . '/src/Reports/Abstracts/ChartReport.php', + 'SlimStat\\Reports\\Abstracts\\SummaryReport' => __DIR__ . '/../..' . '/src/Reports/Abstracts/SummaryReport.php', + 'SlimStat\\Reports\\Abstracts\\TableReport' => __DIR__ . '/../..' . '/src/Reports/Abstracts/TableReport.php', + 'SlimStat\\Reports\\Bootstrap' => __DIR__ . '/../..' . '/src/Reports/Bootstrap.php', + 'SlimStat\\Reports\\Contracts\\ChartableInterface' => __DIR__ . '/../..' . '/src/Reports/Contracts/ChartableInterface.php', + 'SlimStat\\Reports\\Contracts\\FilterableInterface' => __DIR__ . '/../..' . '/src/Reports/Contracts/FilterableInterface.php', + 'SlimStat\\Reports\\Contracts\\PaginatableInterface' => __DIR__ . '/../..' . '/src/Reports/Contracts/PaginatableInterface.php', + 'SlimStat\\Reports\\Contracts\\RenderableInterface' => __DIR__ . '/../..' . '/src/Reports/Contracts/RenderableInterface.php', + 'SlimStat\\Reports\\Contracts\\ReportInterface' => __DIR__ . '/../..' . '/src/Reports/Contracts/ReportInterface.php', + 'SlimStat\\Reports\\Registry\\LegacyReportAdapter' => __DIR__ . '/../..' . '/src/Reports/Registry/LegacyReportAdapter.php', + 'SlimStat\\Reports\\Registry\\ReportFactory' => __DIR__ . '/../..' . '/src/Reports/Registry/ReportFactory.php', + 'SlimStat\\Reports\\Registry\\ReportLoader' => __DIR__ . '/../..' . '/src/Reports/Registry/ReportLoader.php', + 'SlimStat\\Reports\\Registry\\ReportRegistry' => __DIR__ . '/../..' . '/src/Reports/Registry/ReportRegistry.php', + 'SlimStat\\Reports\\Traits\\HasFilters' => __DIR__ . '/../..' . '/src/Reports/Traits/HasFilters.php', + 'SlimStat\\Reports\\Traits\\HasPagination' => __DIR__ . '/../..' . '/src/Reports/Traits/HasPagination.php', + 'SlimStat\\Reports\\Traits\\HasTooltip' => __DIR__ . '/../..' . '/src/Reports/Traits/HasTooltip.php', + 'SlimStat\\Reports\\Types\\Analytics\\LiveAnalyticsReport' => __DIR__ . '/../..' . '/src/Reports/Types/Analytics/LiveAnalyticsReport.php', + 'SlimStat\\Services\\Admin\\ConditionTagEvaluator' => __DIR__ . '/../..' . '/src/Services/Admin/ConditionTagEvaluator.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationActions' => __DIR__ . '/../..' . '/src/Services/Admin/Notification/NotificationActions.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationFactory' => __DIR__ . '/../..' . '/src/Services/Admin/Notification/NotificationFactory.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationFetcher' => __DIR__ . '/../..' . '/src/Services/Admin/Notification/NotificationFetcher.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationManager' => __DIR__ . '/../..' . '/src/Services/Admin/Notification/NotificationManager.php', + 'SlimStat\\Services\\Admin\\Notification\\NotificationProcessor' => __DIR__ . '/../..' . '/src/Services/Admin/Notification/NotificationProcessor.php', 'SlimStat\\Services\\Browscap' => __DIR__ . '/../..' . '/src/Services/Browscap.php', - 'SlimStat\\Services\\GeoIP' => __DIR__ . '/../..' . '/src/Services/GeoIP.php', + 'SlimStat\\Services\\Compliance\\ComplianceManager' => __DIR__ . '/../..' . '/src/Services/Compliance/ComplianceManager.php', + 'SlimStat\\Services\\Compliance\\Integrations\\ComplianceIntegrationInterface' => __DIR__ . '/../..' . '/src/Services/Compliance/Integrations/ComplianceIntegrationInterface.php', + 'SlimStat\\Services\\Compliance\\Regulations\\CCPA\\CCPAServiceProvider' => __DIR__ . '/../..' . '/src/Services/Compliance/Regulations/CCPA/CCPAServiceProvider.php', + 'SlimStat\\Services\\Compliance\\Regulations\\LGPD\\LGPDServiceProvider' => __DIR__ . '/../..' . '/src/Services/Compliance/Regulations/LGPD/LGPDServiceProvider.php', + 'SlimStat\\Services\\CronEventManager' => __DIR__ . '/../..' . '/src/Services/CronEventManager.php', + 'SlimStat\\Services\\GDPRService' => __DIR__ . '/../..' . '/src/Services/GDPRService.php', 'SlimStat\\Services\\GeoService' => __DIR__ . '/../..' . '/src/Services/GeoService.php', - 'SlimStat\\Utils\\InvalidDatabaseException' => __DIR__ . '/../..' . '/src/Utils/InvalidDatabaseException.php', - 'SlimStat\\Utils\\MaxMindDecoder' => __DIR__ . '/../..' . '/src/Utils/MaxMindDecoder.php', - 'SlimStat\\Utils\\MaxMindMetadata' => __DIR__ . '/../..' . '/src/Utils/MaxMindMetadata.php', - 'SlimStat\\Utils\\MaxMindReader' => __DIR__ . '/../..' . '/src/Utils/MaxMindReader.php', - 'SlimStat\\Utils\\MaxMindUtil' => __DIR__ . '/../..' . '/src/Utils/MaxMindUtil.php', + 'SlimStat\\Services\\Geolocation\\AbstractGeoIPProvider' => __DIR__ . '/../..' . '/src/Services/Geolocation/AbstractGeoIPProvider.php', + 'SlimStat\\Services\\Geolocation\\GeolocationFactory' => __DIR__ . '/../..' . '/src/Services/Geolocation/GeolocationFactory.php', + 'SlimStat\\Services\\Geolocation\\GeolocationService' => __DIR__ . '/../..' . '/src/Services/Geolocation/GeolocationService.php', + 'SlimStat\\Services\\Geolocation\\Provider\\CloudflareGeolocationProvider' => __DIR__ . '/../..' . '/src/Services/Geolocation/Provider/CloudflareGeolocationProvider.php', + 'SlimStat\\Services\\Geolocation\\Provider\\DbIpProvider' => __DIR__ . '/../..' . '/src/Services/Geolocation/Provider/DbIpProvider.php', + 'SlimStat\\Services\\Geolocation\\Provider\\GeoServiceProviderInterface' => __DIR__ . '/../..' . '/src/Services/Geolocation/Provider/GeoServiceProviderInterface.php', + 'SlimStat\\Services\\Geolocation\\Provider\\MaxmindGeoIPProvider' => __DIR__ . '/../..' . '/src/Services/Geolocation/Provider/MaxmindGeoIPProvider.php', + 'SlimStat\\Services\\Privacy' => __DIR__ . '/../..' . '/src/Services/Privacy.php', + 'SlimStat\\Services\\Privacy\\ConsentHandler' => __DIR__ . '/../..' . '/src/Services/Privacy/ConsentHandler.php', + 'SlimStat\\Services\\Privacy\\DataEraser' => __DIR__ . '/../..' . '/src/Services/Privacy/DataEraser.php', + 'SlimStat\\Services\\Privacy\\DataExporter' => __DIR__ . '/../..' . '/src/Services/Privacy/DataExporter.php', + 'SlimStat\\Tracker\\Ajax' => __DIR__ . '/../..' . '/src/Tracker/Ajax.php', + 'SlimStat\\Tracker\\Processor' => __DIR__ . '/../..' . '/src/Tracker/Processor.php', + 'SlimStat\\Tracker\\Routing' => __DIR__ . '/../..' . '/src/Tracker/Routing.php', + 'SlimStat\\Tracker\\Session' => __DIR__ . '/../..' . '/src/Tracker/Session.php', + 'SlimStat\\Tracker\\Storage' => __DIR__ . '/../..' . '/src/Tracker/Storage.php', + 'SlimStat\\Tracker\\Tracker' => __DIR__ . '/../..' . '/src/Tracker/Tracker.php', + 'SlimStat\\Tracker\\Utils' => __DIR__ . '/../..' . '/src/Tracker/Utils.php', + 'SlimStat\\Utils\\Consent' => __DIR__ . '/../..' . '/src/Utils/Consent.php', + 'SlimStat\\Utils\\Query' => __DIR__ . '/../..' . '/src/Utils/Query.php', + 'SlimStat\\Utils\\Request' => __DIR__ . '/../..' . '/src/Utils/Request.php', 'SlimStat\\Utils\\UADetector' => __DIR__ . '/../..' . '/src/Utils/UADetector.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\Attribute\\AsCommand' => __DIR__ . '/..' . '/symfony/console/Attribute/AsCommand.php', + 'Symfony\\Component\\Console\\CI\\GithubActionReporter' => __DIR__ . '/..' . '/symfony/console/CI/GithubActionReporter.php', + 'Symfony\\Component\\Console\\Color' => __DIR__ . '/..' . '/symfony/console/Color.php', + 'Symfony\\Component\\Console\\CommandLoader\\CommandLoaderInterface' => __DIR__ . '/..' . '/symfony/console/CommandLoader/CommandLoaderInterface.php', + 'Symfony\\Component\\Console\\CommandLoader\\ContainerCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/ContainerCommandLoader.php', + 'Symfony\\Component\\Console\\CommandLoader\\FactoryCommandLoader' => __DIR__ . '/..' . '/symfony/console/CommandLoader/FactoryCommandLoader.php', + 'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\CompleteCommand' => __DIR__ . '/..' . '/symfony/console/Command/CompleteCommand.php', + 'Symfony\\Component\\Console\\Command\\DumpCompletionCommand' => __DIR__ . '/..' . '/symfony/console/Command/DumpCompletionCommand.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\LazyCommand' => __DIR__ . '/..' . '/symfony/console/Command/LazyCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\Command\\LockableTrait' => __DIR__ . '/..' . '/symfony/console/Command/LockableTrait.php', + 'Symfony\\Component\\Console\\Command\\SignalableCommandInterface' => __DIR__ . '/..' . '/symfony/console/Command/SignalableCommandInterface.php', + 'Symfony\\Component\\Console\\Completion\\CompletionInput' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionInput.php', + 'Symfony\\Component\\Console\\Completion\\CompletionSuggestions' => __DIR__ . '/..' . '/symfony/console/Completion/CompletionSuggestions.php', + 'Symfony\\Component\\Console\\Completion\\Output\\BashCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/BashCompletionOutput.php', + 'Symfony\\Component\\Console\\Completion\\Output\\CompletionOutputInterface' => __DIR__ . '/..' . '/symfony/console/Completion/Output/CompletionOutputInterface.php', + 'Symfony\\Component\\Console\\Completion\\Suggestion' => __DIR__ . '/..' . '/symfony/console/Completion/Suggestion.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Cursor' => __DIR__ . '/..' . '/symfony/console/Cursor.php', + 'Symfony\\Component\\Console\\DependencyInjection\\AddConsoleCommandPass' => __DIR__ . '/..' . '/symfony/console/DependencyInjection/AddConsoleCommandPass.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleErrorEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleErrorEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleSignalEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleSignalEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\MissingInputException' => __DIR__ . '/..' . '/symfony/console/Exception/MissingInputException.php', + 'Symfony\\Component\\Console\\Exception\\NamespaceNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/NamespaceNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\NullOutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/NullOutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Formatter\\WrappableOutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/WrappableOutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\Dumper' => __DIR__ . '/..' . '/symfony/console/Helper/Dumper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableCellStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableCellStyle.php', + 'Symfony\\Component\\Console\\Helper\\TableRows' => __DIR__ . '/..' . '/symfony/console/Helper/TableRows.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\ConsoleSectionOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleSectionOutput.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Output\\TrimmedBufferOutput' => __DIR__ . '/..' . '/symfony/console/Output/TrimmedBufferOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\SignalRegistry\\SignalRegistry' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalRegistry.php', + 'Symfony\\Component\\Console\\SingleCommandApplication' => __DIR__ . '/..' . '/symfony/console/SingleCommandApplication.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Terminal' => __DIR__ . '/..' . '/symfony/console/Terminal.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandCompletionTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandCompletionTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Console\\Tester\\Constraint\\CommandIsSuccessful' => __DIR__ . '/..' . '/symfony/console/Tester/Constraint/CommandIsSuccessful.php', + 'Symfony\\Component\\Console\\Tester\\TesterTrait' => __DIR__ . '/..' . '/symfony/console/Tester/TesterTrait.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Filesystem\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/RuntimeException.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Filesystem\\Path' => __DIR__ . '/..' . '/symfony/filesystem/Path.php', + 'Symfony\\Component\\String\\AbstractString' => __DIR__ . '/..' . '/symfony/string/AbstractString.php', + 'Symfony\\Component\\String\\AbstractUnicodeString' => __DIR__ . '/..' . '/symfony/string/AbstractUnicodeString.php', + 'Symfony\\Component\\String\\ByteString' => __DIR__ . '/..' . '/symfony/string/ByteString.php', + 'Symfony\\Component\\String\\CodePointString' => __DIR__ . '/..' . '/symfony/string/CodePointString.php', + 'Symfony\\Component\\String\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/string/Exception/ExceptionInterface.php', + 'Symfony\\Component\\String\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/string/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\String\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/string/Exception/RuntimeException.php', + 'Symfony\\Component\\String\\Inflector\\EnglishInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/EnglishInflector.php', + 'Symfony\\Component\\String\\Inflector\\FrenchInflector' => __DIR__ . '/..' . '/symfony/string/Inflector/FrenchInflector.php', + 'Symfony\\Component\\String\\Inflector\\InflectorInterface' => __DIR__ . '/..' . '/symfony/string/Inflector/InflectorInterface.php', + 'Symfony\\Component\\String\\LazyString' => __DIR__ . '/..' . '/symfony/string/LazyString.php', + 'Symfony\\Component\\String\\Slugger\\AsciiSlugger' => __DIR__ . '/..' . '/symfony/string/Slugger/AsciiSlugger.php', + 'Symfony\\Component\\String\\Slugger\\SluggerInterface' => __DIR__ . '/..' . '/symfony/string/Slugger/SluggerInterface.php', + 'Symfony\\Component\\String\\UnicodeString' => __DIR__ . '/..' . '/symfony/string/UnicodeString.php', + 'Symfony\\Contracts\\Service\\Attribute\\Required' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/Required.php', + 'Symfony\\Contracts\\Service\\Attribute\\SubscribedService' => __DIR__ . '/..' . '/symfony/service-contracts/Attribute/SubscribedService.php', + 'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php', + 'Symfony\\Contracts\\Service\\ServiceCollectionInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceCollectionInterface.php', + 'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php', + 'Symfony\\Contracts\\Service\\ServiceMethodsSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceMethodsSubscriberTrait.php', + 'Symfony\\Contracts\\Service\\ServiceProviderInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceProviderInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberInterface.php', + 'Symfony\\Contracts\\Service\\ServiceSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceSubscriberTrait.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/Grapheme.php', + 'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Normalizer.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php', + 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', + 'Symfony\\Polyfill\\Php80\\PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/PhpToken.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', ); public static function getInitializer(ClassLoader $loader) @@ -2,23 +2,23 @@ "packages": [ { "name": "guzzlehttp/guzzle", - "version": "7.8.1", - "version_normalized": "7.8.1.0", + "version": "7.10.0", + "version_normalized": "7.10.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -29,9 +29,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -39,7 +39,7 @@ "ext-intl": "Required for Internationalized Domain Name (IDN) support", "psr/log": "Required for using the Log middleware" }, - "time": "2023-12-03T20:35:24+00:00", + "time": "2025-08-23T22:36:01+00:00", "type": "library", "extra": { "bamarni-bin": { @@ -111,7 +111,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" }, "funding": [ { @@ -131,17 +131,17 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.2", - "version_normalized": "2.0.2.0", + "version": "2.3.0", + "version_normalized": "2.3.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "481557b130ef3790cf82b713667b43030dc9c957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", "shasum": "" }, "require": { @@ -149,9 +149,9 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, - "time": "2023-12-03T20:19:20+00:00", + "time": "2025-08-22T14:34:08+00:00", "type": "library", "extra": { "bamarni-bin": { @@ -197,7 +197,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.2" + "source": "https://github.com/guzzle/promises/tree/2.3.0" }, "funding": [ { @@ -217,17 +217,17 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", - "version_normalized": "2.6.2.0", + "version": "2.8.0", + "version_normalized": "2.8.0.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "21dc724a0583619cd1652f673303492272778051" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051", + "reference": "21dc724a0583619cd1652f673303492272778051", "shasum": "" }, "require": { @@ -242,13 +242,13 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, - "time": "2023-12-03T20:05:35+00:00", + "time": "2025-08-23T21:21:41+00:00", "type": "library", "extra": { "bamarni-bin": { @@ -316,7 +316,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.2" + "source": "https://github.com/guzzle/psr7/tree/2.8.0" }, "funding": [ { @@ -425,17 +425,17 @@ }, { "name": "league/mime-type-detection", - "version": "1.15.0", - "version_normalized": "1.15.0.0", + "version": "1.16.0", + "version_normalized": "1.16.0.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", - "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", "shasum": "" }, "require": { @@ -447,7 +447,7 @@ "phpstan/phpstan": "^0.12.68", "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, - "time": "2024-01-28T23:22:08+00:00", + "time": "2024-09-21T08:32:55+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -468,7 +468,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" }, "funding": [ { @@ -1017,17 +1017,17 @@ }, { "name": "symfony/console", - "version": "v5.4.40", - "version_normalized": "5.4.40.0", + "version": "v5.4.47", + "version_normalized": "5.4.47.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/aa73115c0c24220b523625bfcfa655d7d73662dd", - "reference": "aa73115c0c24220b523625bfcfa655d7d73662dd", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { @@ -1065,7 +1065,7 @@ "symfony/lock": "", "symfony/process": "" }, - "time": "2024-05-31T14:33:22+00:00", + "time": "2024-11-06T11:30:55+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1099,7 +1099,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.40" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -1119,31 +1119,31 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", - "version_normalized": "3.5.0.0", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { "php": ">=8.1" }, - "time": "2024-04-18T09:32:20+00:00", + "time": "2024-09-25T14:21:43+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "installation-source": "dist", @@ -1169,7 +1169,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -1189,17 +1189,17 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.40", - "version_normalized": "5.4.40.0", + "version": "v5.4.45", + "version_normalized": "5.4.45.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995" + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/26dd9912df6940810ea00f8f53ad48d6a3424995", - "reference": "26dd9912df6940810ea00f8f53ad48d6a3424995", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54", + "reference": "57c8294ed37d4a055b77057827c67f9558c95c54", "shasum": "" }, "require": { @@ -1211,7 +1211,7 @@ "require-dev": { "symfony/process": "^5.4|^6.4" }, - "time": "2024-05-31T14:33:22+00:00", + "time": "2024-10-22T13:05:35+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1239,7 +1239,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.40" + "source": "https://github.com/symfony/filesystem/tree/v5.4.45" }, "funding": [ { @@ -1259,21 +1259,21 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -1281,12 +1281,12 @@ "suggest": { "ext-ctype": "For best performance" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2024-09-09T11:45:10+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1321,7 +1321,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -1333,6 +1333,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -1341,31 +1345,31 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2025-06-27T09:58:17+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1402,7 +1406,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" }, "funding": [ { @@ -1414,6 +1418,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -1422,31 +1430,31 @@ }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "reference": "3833d7255cc303546435cb650316bff708a1c75c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2024-09-09T11:45:10+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1486,7 +1494,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" }, "funding": [ { @@ -1498,6 +1506,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -1506,21 +1518,22 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -1528,12 +1541,12 @@ "suggest": { "ext-mbstring": "For best performance" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2024-12-23T08:48:59+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1569,7 +1582,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -1581,6 +1594,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -1589,28 +1606,28 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", - "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2024-09-09T11:45:10+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1648,7 +1665,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.33.0" }, "funding": [ { @@ -1660,6 +1677,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -1668,28 +1689,28 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.29.0", - "version_normalized": "1.29.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", - "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, - "time": "2024-01-29T20:11:03+00:00", + "time": "2025-01-02T08:10:11+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1731,7 +1752,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -1743,6 +1764,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -1751,17 +1776,17 @@ }, { "name": "symfony/service-contracts", - "version": "v3.5.0", - "version_normalized": "3.5.0.0", + "version": "v3.6.0", + "version_normalized": "3.6.0.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -1772,15 +1797,15 @@ "conflict": { "ext-psr": "<1.1|>=2" }, - "time": "2024-04-18T09:32:20+00:00", + "time": "2025-04-25T09:37:31+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.5-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" } }, "installation-source": "dist", @@ -1817,7 +1842,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -1837,17 +1862,17 @@ }, { "name": "symfony/string", - "version": "v6.4.8", - "version_normalized": "6.4.8.0", + "version": "v6.4.26", + "version_normalized": "6.4.26.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d" + "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/a147c0f826c4a1f3afb763ab8e009e37c877a44d", - "reference": "a147c0f826c4a1f3afb763ab8e009e37c877a44d", + "url": "https://api.github.com/repos/symfony/string/zipball/5621f039a71a11c87c106c1c598bdcd04a19aeea", + "reference": "5621f039a71a11c87c106c1c598bdcd04a19aeea", "shasum": "" }, "require": { @@ -1861,13 +1886,12 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", "symfony/http-client": "^5.4|^6.0|^7.0", "symfony/intl": "^6.2|^7.0", "symfony/translation-contracts": "^2.5|^3.0", "symfony/var-exporter": "^5.4|^6.0|^7.0" }, - "time": "2024-05-31T14:49:08+00:00", + "time": "2025-09-11T14:32:46+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1906,7 +1930,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.8" + "source": "https://github.com/symfony/string/tree/v6.4.26" }, "funding": [ { @@ -1918,6 +1942,10 @@ "type": "github" }, { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } @@ -3,7 +3,7 @@ 'name' => 'wp-slimstat/wp-slimstat', 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '2b0f0c238f368de43db364d3b2070a07a2fcba52', + 'reference' => '792624a3d0a8352ea464892a2355a4e68d40f734', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -11,27 +11,27 @@ ), 'versions' => array( 'guzzlehttp/guzzle' => array( - 'pretty_version' => '7.8.1', - 'version' => '7.8.1.0', - 'reference' => '41042bc7ab002487b876a0683fc8dce04ddce104', + 'pretty_version' => '7.10.0', + 'version' => '7.10.0.0', + 'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/guzzle', 'aliases' => array(), 'dev_requirement' => false, ), 'guzzlehttp/promises' => array( - 'pretty_version' => '2.0.2', - 'version' => '2.0.2.0', - 'reference' => 'bbff78d96034045e58e13dedd6ad91b5d1253223', + 'pretty_version' => '2.3.0', + 'version' => '2.3.0.0', + 'reference' => '481557b130ef3790cf82b713667b43030dc9c957', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/promises', 'aliases' => array(), 'dev_requirement' => false, ), 'guzzlehttp/psr7' => array( - 'pretty_version' => '2.6.2', - 'version' => '2.6.2.0', - 'reference' => '45b30f99ac27b5ca93cb4831afe16285f57b8221', + 'pretty_version' => '2.8.0', + 'version' => '2.8.0.0', + 'reference' => '21dc724a0583619cd1652f673303492272778051', 'type' => 'library', 'install_path' => __DIR__ . '/../guzzlehttp/psr7', 'aliases' => array(), @@ -47,9 +47,9 @@ 'dev_requirement' => false, ), 'league/mime-type-detection' => array( - 'pretty_version' => '1.15.0', - 'version' => '1.15.0.0', - 'reference' => 'ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301', + 'pretty_version' => '1.16.0', + 'version' => '1.16.0.0', + 'reference' => '2d6702ff215bf922936ccc1ad31007edc76451b9', 'type' => 'library', 'install_path' => __DIR__ . '/../league/mime-type-detection', 'aliases' => array(), @@ -173,99 +173,99 @@ 'dev_requirement' => false, ), 'symfony/console' => array( - 'pretty_version' => 'v5.4.40', - 'version' => '5.4.40.0', - 'reference' => 'aa73115c0c24220b523625bfcfa655d7d73662dd', + 'pretty_version' => 'v5.4.47', + 'version' => '5.4.47.0', + 'reference' => 'c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/console', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/deprecation-contracts' => array( - 'pretty_version' => 'v3.5.0', - 'version' => '3.5.0.0', - 'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1', + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/filesystem' => array( - 'pretty_version' => 'v5.4.40', - 'version' => '5.4.40.0', - 'reference' => '26dd9912df6940810ea00f8f53ad48d6a3424995', + 'pretty_version' => 'v5.4.45', + 'version' => '5.4.45.0', + 'reference' => '57c8294ed37d4a055b77057827c67f9558c95c54', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/filesystem', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => 'ef4d7e442ca910c4764bce785146269b30cb5fc4', + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-grapheme' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => '32a9da87d7b3245e09ac426c83d334ae9f06f80f', + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '380872130d3a5dd3ace2f4010d95125fde5d5c70', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-intl-normalizer' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => 'bc45c394692b948b4d383a08d7753968bed9a83d', + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '3833d7255cc303546435cb650316bff708a1c75c', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-mbstring' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec', + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php73' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => '21bd091060673a1177ae842c0ef8fe30893114d2', + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php73', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.29.0', - 'version' => '1.29.0.0', - 'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b', + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/service-contracts' => array( - 'pretty_version' => 'v3.5.0', - 'version' => '3.5.0.0', - 'reference' => 'bd1d9e59a81d8fa4acdcea3f617c581f7475a80f', + 'pretty_version' => 'v3.6.0', + 'version' => '3.6.0.0', + 'reference' => 'f021b05a130d35510bd6b25fe9053c2a8a15d5d4', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/service-contracts', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/string' => array( - 'pretty_version' => 'v6.4.8', - 'version' => '6.4.8.0', - 'reference' => 'a147c0f826c4a1f3afb763ab8e009e37c877a44d', + 'pretty_version' => 'v6.4.26', + 'version' => '6.4.26.0', + 'reference' => '5621f039a71a11c87c106c1c598bdcd04a19aeea', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/string', 'aliases' => array(), @@ -285,7 +285,7 @@ 'wp-slimstat/wp-slimstat' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', - 'reference' => '2b0f0c238f368de43db364d3b2070a07a2fcba52', + 'reference' => '792624a3d0a8352ea464892a2355a4e68d40f734', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -27,12 +27,23 @@ class InstalledVersions { /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + + /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null */ private static $installed; /** + * @var bool + */ + private static $installedIsLocalDir; + + /** * @var bool|null */ private static $canGetVendors; @@ -309,6 +320,24 @@ { self::$installed = $data; self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; } /** @@ -322,19 +351,27 @@ } $installed = array(); + $copiedLocalDir = false; if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */ $required = require $vendorDir.'/composer/installed.php'; - $installed[] = self::$installedByVendor[$vendorDir] = $required; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; } } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } } } @@ -350,7 +387,7 @@ } } - if (self::$installed !== array()) { + if (self::$installed !== array() && !$copiedLocalDir) { $installed[] = self::$installed; } Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor: guzzlehttp Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor: league Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor: matthiasmullie Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor: psr Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: console Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: filesystem Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: polyfill-ctype Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: polyfill-intl-grapheme Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: polyfill-intl-normalizer Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: polyfill-mbstring Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: polyfill-php73 Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: polyfill-php80 Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: service-contracts Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor/symfony: string Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/vendor: veronalabs Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/views: components @@ -17,7 +17,23 @@ ] ); -$availableRange = $args['end'] - $args['start']; +$chart_args = is_array($args) ? $args : []; +if (isset($chart_args['args']) && is_array($chart_args['args'])) { + $chart_args = $chart_args['args']; +} + +$chart_args = wp_parse_args( + $chart_args, + [ + 'start' => 0, + 'end' => 0, + 'granularity' => 'daily', + 'chart_type' => 'line', + 'id' => '', + ] +); + +$availableRange = $chart_args['end'] - $chart_args['start']; $disableYearly = $availableRange < (365 * 86400); // Less than 1 year of data $disableMonthly = $availableRange < (30 * 86400); // Less than 1 month of data $disableWeekly = $availableRange < (7 * 86400); // Less than 1 week of data @@ -33,48 +49,68 @@ 'v2' => (int) ($data['totals'][1]->v2 ?? 0), ], ]; +$is_empty = (0 === ($totals['current']['v1'] ?? 0) && 0 === ($totals['current']['v2'] ?? 0)); ?> -<div class="slimstat-chart-wrap"> +<div + class="slimstat-chart-wrap <?php echo esc_attr(isset($chart_args['chart_type']) && $chart_args['chart_type'] === 'bar' ? 'chart-bar' : 'chart-line'); ?>"> <div class="slimstat-chart-controls"> <select - id="slimstat_granularity_<?php echo esc_attr($args['id']); ?>" - name="granularity_<?php echo esc_attr($args['id']); ?>" + id="slimstat_granularity_<?php echo esc_attr($chart_args['id']); ?>" + name="granularity_<?php echo esc_attr($chart_args['id']); ?>" class="slimstat-granularity-select"> <option value="yearly" <?php echo $disableYearly ? 'disabled' : ''; ?> - <?php selected($args['granularity'], 'yearly'); ?>><?php echo esc_html($translations['yearly']); ?> + <?php selected($chart_args['granularity'], 'yearly'); ?>><?php echo esc_html($translations['yearly']); ?> </option> <option value="monthly" <?php echo $disableMonthly ? 'disabled' : ''; ?> - <?php selected($args['granularity'], 'monthly'); ?>><?php echo esc_html($translations['monthly']); ?> + <?php selected($chart_args['granularity'], 'monthly'); ?>><?php echo esc_html($translations['monthly']); ?> </option> <option value="weekly" <?php echo $disableWeekly ? 'disabled' : ''; ?> - <?php selected($args['granularity'], 'weekly'); ?>><?php echo esc_html($translations['weekly']); ?> + <?php selected($chart_args['granularity'], 'weekly'); ?>><?php echo esc_html($translations['weekly']); ?> </option> <option value="daily" <?php echo $disableDaily ? 'disabled' : ''; ?> - <?php selected($args['granularity'], 'daily'); ?>><?php echo esc_html($translations['daily']); ?> + <?php selected($chart_args['granularity'], 'daily'); ?>><?php echo esc_html($translations['daily']); ?> </option> <option value="hourly" <?php echo $disableHourly ? 'disabled' : ''; ?> - <?php selected($args['granularity'], 'hourly'); ?>><?php echo esc_html($translations['hourly']); ?> + <?php selected($chart_args['granularity'], 'hourly'); ?>><?php echo esc_html($translations['hourly']); ?> </option> </select> </div> - <div id="slimstat_chart_data_<?php echo esc_attr($args['id']); ?>" - data-args="<?php echo esc_attr(json_encode($args)); ?>" - data-data="<?php echo esc_attr(json_encode($data)); ?>" - data-prev-data="<?php echo esc_attr(json_encode($prevData)); ?>" - data-granularity="<?php echo esc_attr($args['granularity']); ?>" - data-chart-labels="<?php echo esc_attr(json_encode($chartLabels)); ?>" - data-translations="<?php echo esc_attr(json_encode($translations)); ?>" - data-totals="<?php echo esc_attr(json_encode($totals ?? [])); ?>"> + <div id="slimstat_chart_data_<?php echo esc_attr($chart_args['id']); ?>" + data-args="<?php echo esc_attr(wp_json_encode($chart_args)); ?>" + data-data="<?php echo esc_attr(wp_json_encode($data)); ?>" + data-prev-data="<?php echo esc_attr(wp_json_encode($prevData)); ?>" + data-granularity="<?php echo esc_attr($chart_args['granularity']); ?>" + data-chart-type="<?php echo esc_attr($chart_args['chart_type'] ?? 'line'); ?>" + data-chart-labels="<?php echo esc_attr(wp_json_encode($chartLabels)); ?>" + data-translations="<?php echo esc_attr(wp_json_encode($translations)); ?>" + data-totals="<?php echo esc_attr(wp_json_encode($totals ?? [])); ?>"> + </div> + <div id="slimstat-postbox-custom-legend_<?php echo esc_attr($chart_args['id']); ?>" + class="slimstat-postbox-chart--items"> + <?php if ($is_empty): ?> + <?php + $label_one = $chartLabels[0] ?? __('Search Terms', 'wp-slimstat'); + $label_two = $chartLabels[1] ?? __('Unique Terms', 'wp-slimstat'); + ?> + <div class="slimstat-postbox-chart--item"> + <span class="slimstat-postbox-chart--item-label"><?php echo esc_html($label_one); ?></span> + <span class="slimstat-postbox-chart--item--color" style="background-color: #e8294c"></span> + <span class="slimstat-postbox-chart--item-value">0</span> + </div> + <div class="slimstat-postbox-chart--item"> + <span class="slimstat-postbox-chart--item-label"><?php echo esc_html($label_two); ?></span> + <span class="slimstat-postbox-chart--item--color" style="background-color: #2b76f6"></span> + <span class="slimstat-postbox-chart--item-value">0</span> + </div> + <?php endif; ?> </div> - <div id="slimstat-postbox-custom-legend_<?php echo esc_attr($args['id']); ?>" - class="slimstat-postbox-chart--items"></div> <canvas - id="slimstat_chart_<?php echo esc_attr($args['id']); ?>" + id="slimstat_chart_<?php echo esc_attr($chart_args['id']); ?>" class="slimstat-postbox-chart--canvas" height="240px"></canvas> <?php if (defined('DOING_AJAX') && DOING_AJAX): ?> <script> reinitializeSlimStatCharts( - "<?php echo $args['id']; ?>") + "<?php echo esc_js($chart_args['id']); ?>") </script> <?php endif; ?> -</div> \ No newline at end of file +</div> Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0/views: reports @@ -1,4 +1,4 @@ -import Fingerprint2 from "fingerprintjs2"; +import FingerprintJS from "@fingerprintjs/fingerprintjs"; /** * SlimStat: Browser tracking helper (refactored for maintainability) @@ -132,7 +132,10 @@ } function getComponentValue(components, key, def) { - for (var i = 0; i < components.length; i++) if (components[i].key === key) return components[i].value; + // FingerprintJS v4 API - components is now an object with component names as keys + if (components && components[key] && components[key].value !== undefined) { + return components[key].value; + } return def; } @@ -168,20 +171,54 @@ } // -------------------------- Fingerprint -------------------------- // - function initFingerprintHash(components) { + function initFingerprintHash(result) { try { - var values = components.map(function (c) { - return c.value; - }); - fingerprintHash = Fingerprint2.x64hash128(values.join(""), 31); + // FingerprintJS v4 API - result contains visitorId and components + if (result && result.visitorId) { + fingerprintHash = result.visitorId; + return; + } + // Graceful fallback + fingerprintHash = ""; } catch (e) { fingerprintHash = ""; // graceful fallback } } function buildSlimStatData(components) { - var screenres = getComponentValue(components, "screenResolution", [0, 0]); - return "&sw=" + screenres[0] + "&sh=" + screenres[1] + "&bw=" + window.innerWidth + "&bh=" + window.innerHeight + "&sl=" + getServerLatency() + "&pp=" + getPagePerformance() + "&fh=" + fingerprintHash + "&tz=" + getComponentValue(components, "timezoneOffset", 0); + // Components are optional; compute directly if not provided + // FingerprintJS v4 returns components as an object, not an array + var hasComponents = components && typeof components === "object" && !Array.isArray(components); + + var screenres = [0, 0]; + try { + if (hasComponents) { + screenres = getComponentValue(components, "screenResolution", [0, 0]); + } + // Fallback to window.screen if components not available or screenResolution not found + if (!screenres || screenres[0] === 0) { + if (window.screen) { + screenres = [window.screen.width || 0, window.screen.height || 0]; + } + } + } catch (e) { + screenres = [0, 0]; + } + + var tzOffset = 0; + try { + if (hasComponents) { + tzOffset = getComponentValue(components, "timezoneOffset", 0); + } + // Fallback to Date API if components not available or timezoneOffset not found + if (tzOffset === 0 && !hasComponents) { + tzOffset = new Date().getTimezoneOffset(); + } + } catch (e) { + tzOffset = 0; + } + + return "&sw=" + screenres[0] + "&sh=" + screenres[1] + "&bw=" + window.innerWidth + "&bh=" + window.innerHeight + "&sl=" + getServerLatency() + "&pp=" + getPagePerformance() + "&fh=" + fingerprintHash + "&tz=" + tzOffset; } // -------------------------- Transport -------------------------- // @@ -243,7 +280,14 @@ }, delay); } else { // Max attempts reached, move to offline storage - storeOffline(item.payload); + SlimStat.store_offline(item.payload); + if (item.opts && typeof item.opts.onComplete === "function") { + item.opts.onComplete(false); + } + } + } else { + if (item.opts && typeof item.opts.onComplete === "function") { + item.opts.onComplete(!!success); } } queueInFlight = false; @@ -258,8 +302,9 @@ var params = currentSlimStatParams(); var payload = item.payload; var useBeacon = item.useBeacon; - var transports = ["rest", "ajax", "adblock"]; - var endpoints = { rest: params.ajaxurl_rest, ajax: params.ajaxurl_ajax, adblock: params.ajaxurl_adblock }; + var requiresIdResponse = isEmpty(params.id) || isNaN(parseInt(params.id, 10)) || parseInt(params.id, 10) <= 0; + var transports = ["rest", "ajax", "adblock_bypass"]; + var endpoints = { rest: params.ajaxurl_rest, ajax: params.ajaxurl_ajax, adblock_bypass: params.ajaxurl_adblock }; var selected = params.transport; var order = [selected].concat( transports.filter(function (t) { @@ -293,8 +338,22 @@ var parsed = parseInt(xhr.responseText, 10); if (!isNaN(parsed) && parsed > 0) { params.id = xhr.responseText; // store new id + // Mark that we've successfully tracked the initial pageview for this load + try { + window.slimstatPageviewTracked = true; + } catch (trackErr) { + /* ignore */ + } flushPendingInteractions(); // Flush buffered interactions now that we have an ID } + + // Initial pageview creation must return a valid pageview ID. + // Treat empty/non-numeric responses as failures so fallback/retry can run. + if (requiresIdResponse && (isNaN(parsed) || parsed <= 0)) { + if (onFail) onFail(); + return; + } + callback(true); } else { // Non-200 status is a failure, trigger retry/failover @@ -457,7 +516,579 @@ } // -------------------------- Pageview Logic -------------------------- // - var FP_EXCLUDES = { excludes: { adBlock: true, addBehavior: true, userAgent: true, canvas: true, webgl: true, colorDepth: true, deviceMemory: true, hardwareConcurrency: true, sessionStorage: true, localStorage: true, indexedDb: true, openDatabase: true, cpuClass: true, plugins: true, webglVendorAndRenderer: true, hasLiedLanguages: true, hasLiedResolution: true, hasLiedOs: true, hasLiedBrowser: true, fonts: true, audio: true } }; + // FP_EXCLUDES retained for backward compatibility, not used by FingerprintJS v4 + var FP_EXCLUDES = {}; + + // -------------------------- Consent Helpers -------------------------- // + var lastConsentSnapshot = null; + var CONSENT_UPGRADE_STATE_KEY = "slimstat_consent_upgrade_state"; + var CONSENT_UPGRADE_TS_KEY = "slimstat_consent_upgrade_ts"; + + function getConsentUpgradeStore(key) { + try { + return sessionStorage.getItem(key) || ""; + } catch (e) { + return window[key] || ""; + } + } + + function setConsentUpgradeStore(key, value) { + try { + if (value === "" || value === null || typeof value === "undefined") { + sessionStorage.removeItem(key); + } else { + sessionStorage.setItem(key, value); + } + } catch (e) { + if (value === "" || value === null || typeof value === "undefined") { + delete window[key]; + } else { + window[key] = value; + } + } + } + + function markConsentUpgradePending() { + setConsentUpgradeStore(CONSENT_UPGRADE_STATE_KEY, "pending"); + setConsentUpgradeStore(CONSENT_UPGRADE_TS_KEY, Date.now().toString()); + } + + function markConsentUpgradeDone(success) { + if (success) { + setConsentUpgradeStore(CONSENT_UPGRADE_STATE_KEY, "done"); + setConsentUpgradeStore(CONSENT_UPGRADE_TS_KEY, Date.now().toString()); + } else { + setConsentUpgradeStore(CONSENT_UPGRADE_STATE_KEY, ""); + setConsentUpgradeStore(CONSENT_UPGRADE_TS_KEY, ""); + } + } + + function hasConsentUpgradeSucceeded() { + return getConsentUpgradeStore(CONSENT_UPGRADE_STATE_KEY) === "done"; + } + + function claimConsentUpgradeSlot(force) { + if (force === true) { + markConsentUpgradePending(); + return true; + } + + var state = getConsentUpgradeStore(CONSENT_UPGRADE_STATE_KEY); + if ("done" === state) { + return false; + } + + if ("pending" === state) { + var ts = parseInt(getConsentUpgradeStore(CONSENT_UPGRADE_TS_KEY) || "0", 10); + if (Date.now() - ts < 5000) { + return false; + } + } + + markConsentUpgradePending(); + return true; + } + + function requestConsentUpgrade(extraOptions) { + extraOptions = extraOptions || {}; + var force = extraOptions.force === true; + + if (!claimConsentUpgradeSlot(force)) { + return false; + } + + var requestOptions = { + isConsentRetry: true, + consentUpgrade: true, + }; + + if (extraOptions.consent) { + requestOptions.consent = extraOptions.consent; + } + if (extraOptions.consentNonce) { + requestOptions.consentNonce = extraOptions.consentNonce; + } + + SlimStat._send_pageview(requestOptions); + return true; + } + + function isFunction(value) { + return typeof value === "function"; + } + + function isObject(value) { + return value !== null && typeof value === "object"; + } + + function getCookieStrict(name) { + if (!name) return null; + try { + var safeName = name.replace(/([.$?*|{}()\[\]\\\/\+^])/g, "\\$1"); + var pattern = "(?:^|;)\\s*" + safeName + "=([^;]*)"; + var match = document.cookie.match(pattern); + return match ? decodeURIComponent(match[1]) : null; + } catch (e) { + return null; + } + } + + function detectRealCookieBannerConsent(category) { + try { + // Latest API (RCB 4.x+): window.rcb() function + if (isFunction(window.rcb)) { + try { + var rcbConsent = window.rcb("consent", category); + if (rcbConsent === true || rcbConsent === false) return !!rcbConsent; + if (isObject(rcbConsent) && "cookie" in rcbConsent) return !!rcbConsent.cookie; + if (isObject(rcbConsent) && "consent" in rcbConsent) return !!rcbConsent.consent; + } catch (e) {} + } + + // New API: window.RCB.consent.get() + if (isObject(window.RCB) && isObject(window.RCB.consent) && isFunction(window.RCB.consent.get)) { + var rcbNew = window.RCB.consent.get(category); + if (rcbNew === true || rcbNew === false) return !!rcbNew; + if (isObject(rcbNew) && "cookie" in rcbNew) return !!rcbNew.cookie; + if (isObject(rcbNew) && "consent" in rcbNew) return !!rcbNew.consent; + } + + // Current API: window.rcbConsentManager.getUserDecision() + if (isObject(window.rcbConsentManager) && isFunction(window.rcbConsentManager.getUserDecision)) { + var decision = window.rcbConsentManager.getUserDecision(); + if (decision && decision.decision) { + if (decision.decision === "all") return true; + if (typeof decision.decision === "object") { + var value = decision.decision[category]; + if (typeof value === "boolean") return value; + if (Array.isArray(value)) return value.length > 0; + } + } + } + + // Legacy API: window.realCookieBanner.consent.get() + var rcb = window.realCookieBanner || window.RealCookieBanner || null; + if (isObject(rcb) && isObject(rcb.consent) && isFunction(rcb.consent.get)) { + var consent = rcb.consent.get(category); + if (consent === true || consent === false) return !!consent; + if (isObject(consent) && "cookie" in consent) return !!consent.cookie; + if (consent) return true; + } + + // Very old API: window.__rcb + var legacy = window.__rcb || window.__RCB || null; + if (isObject(legacy) && legacy.consent) { + var legacyVal = legacy.consent[category]; + if (typeof legacyVal === "boolean") return legacyVal; + if (Array.isArray(legacyVal)) return legacyVal.length > 0; + } + + // Cookie fallback + var possibleNames = ["real_cookie_banner", "rcb_consent", "rcb_acceptance", "real_cookie_consent", "rcb-consent"]; + for (var i = 0; i < possibleNames.length; i++) { + var raw = getCookieStrict(possibleNames[i]); + if (!raw) { + continue; + } + try { + var parsed = JSON.parse(raw); + if (parsed) { + if (typeof parsed[category] === "boolean") return parsed[category]; + if (typeof parsed.consent === "boolean") return parsed.consent; + if (typeof parsed[category] === "object" && parsed[category].cookie !== undefined) return !!parsed[category].cookie; + } + } catch (err) { + var normalized = raw.toLowerCase(); + if (raw.indexOf(category) !== -1 || raw === "1" || normalized === "true" || normalized === "all" || normalized === "accepted") { + return true; + } + } + } + } catch (error) {} + return null; + } + + function detectWPConsentAPI(category) { + try { + if (isFunction(window.wp_has_service_consent)) { + try { + var serviceConsent = window.wp_has_service_consent(category); + if (serviceConsent) return true; + if (isFunction(window.wp_is_service_denied) && window.wp_is_service_denied(category)) { + return false; + } + } catch (err) {} + } + + if (isFunction(window.wp_has_consent)) { + try { + var hasConsent = window.wp_has_consent(category); + if (hasConsent) return true; + return false; + } catch (err2) {} + } + + var consentObj = window.wpConsent || window.WPConsent || null; + if (isObject(consentObj) && isFunction(consentObj.get)) { + var value = consentObj.get(category); + if (value === true || value === false) { + return !!value; + } + } + } catch (err3) {} + return null; + } + + function detectSlimStatBanner(consentCookieName, category) { + try { + var cookieName = consentCookieName || "slimstat_gdpr_consent"; + var value = getCookieStrict(cookieName); + if (!value) { + return null; + } + if (value === "accepted") { + return true; + } + if (value === "denied") { + return false; + } + try { + var parsed = JSON.parse(value); + if (parsed && parsed[category] !== undefined) { + return !!parsed[category]; + } + } catch (err) { + /* ignore */ + } + return value.length > 0; + } catch (err4) { + return null; + } + } + + function normalizeConsent(raw) { + var normalized = { + functional: "deny", + statistics: "deny", + statistics_anonymous: "deny", + marketing: "deny", + }; + + if (typeof raw === "boolean") { + normalized.statistics = raw ? "allow" : "deny"; + return normalized; + } + + if (typeof raw === "string") { + if (raw === "accepted" || raw === "allow" || raw === "grant") { + normalized.statistics = "allow"; + } else if (raw === "denied" || raw === "deny" || raw === "revoke") { + normalized.statistics = "deny"; + } + return normalized; + } + + if (!isObject(raw) && !Array.isArray(raw)) { + return normalized; + } + + var data = raw; + if (Array.isArray(raw)) { + data = { allowed: raw }; + } + + if (Array.isArray(data.allowed)) { + for (var i = 0; i < data.allowed.length; i++) { + var category = data.allowed[i]; + if (normalized.hasOwnProperty(category)) { + normalized[category] = "allow"; + } + } + return normalized; + } + + if (Array.isArray(data.denied)) { + for (var j = 0; j < data.denied.length; j++) { + var deniedCategory = data.denied[j]; + if (normalized.hasOwnProperty(deniedCategory)) { + normalized[deniedCategory] = "deny"; + } + } + } + + var categories = ["functional", "statistics", "statistics_anonymous", "marketing"]; + for (var k = 0; k < categories.length; k++) { + var cat = categories[k]; + if (data[cat] !== undefined) { + if (typeof data[cat] === "boolean") { + normalized[cat] = data[cat] ? "allow" : "deny"; + } else if (typeof data[cat] === "string") { + normalized[cat] = ["allow", "accepted", "grant", "true"].indexOf(data[cat]) !== -1 ? "allow" : "deny"; + } + } else if (data.groups && data.groups[cat] !== undefined) { + var groupValue = data.groups[cat]; + if (typeof groupValue === "boolean") { + normalized[cat] = groupValue ? "allow" : "deny"; + } else if (typeof groupValue === "string") { + normalized[cat] = ["allow", "accepted", "grant", "true"].indexOf(groupValue) !== -1 ? "allow" : "deny"; + } + } else if (data.decision !== undefined) { + if (data.decision === "all") { + normalized[cat] = "allow"; + } else if (isObject(data.decision) && data.decision[cat] !== undefined) { + var decisionValue = data.decision[cat]; + if (typeof decisionValue === "boolean") { + normalized[cat] = decisionValue ? "allow" : "deny"; + } else if (typeof decisionValue === "string") { + normalized[cat] = ["allow", "accepted", "grant", "true"].indexOf(decisionValue) !== -1 ? "allow" : "deny"; + } + } + } + } + + return normalized; + } + + function sendConsentChangeToServer(source, parsedConsent, pageviewId) { + try { + var params = currentSlimStatParams(); + var nonce = params.wp_rest_nonce || ""; + var restUrl = ""; + + // Try to get REST URL from params first + if (params.resturl) { + restUrl = params.resturl; + } else if (typeof window.wpApiSettings !== "undefined" && window.wpApiSettings.root) { + restUrl = window.wpApiSettings.root; + } else { + // Fallback: construct REST URL from current site URL + var siteUrl = window.location.origin; + if (params.baseurl && params.baseurl !== "/") { + var basePath = params.baseurl.replace(/\/$/, ""); + restUrl = siteUrl + basePath + "/wp-json/"; + } else { + restUrl = siteUrl + "/wp-json/"; + } + } + + // Ensure restUrl ends with / + if (restUrl && restUrl.charAt(restUrl.length - 1) !== "/") { + restUrl += "/"; + } + + var endpoint = restUrl + "slimstat/v1/consent-change"; + var payload = { + source: source, + parsed: parsedConsent, + ts: Date.now(), + mode: { + gdprEnabled: params.gdpr_enabled !== "off", + anonymousTrackingEnabled: params.anonymous_tracking === "on", + }, + nonce: nonce, + }; + + if (pageviewId) { + payload.pageview_id = String(pageviewId); + } + + if (typeof window.fetch === "function") { + fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-WP-Nonce": nonce, + }, + credentials: "same-origin", + body: JSON.stringify(payload), + }) + .then(function (response) { + if (!response.ok) { + return; + } + return response.json(); + }) + .catch(function () {}); + } else { + var xhr = new XMLHttpRequest(); + xhr.open("POST", endpoint, true); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("X-WP-Nonce", nonce); + xhr.onload = function () { + if (xhr.status >= 200 && xhr.status < 300) { + try { + var responseData = JSON.parse(xhr.responseText); + } catch (parseError) { + /* ignore */ + } + } + }; + xhr.onerror = function () {}; + xhr.send(JSON.stringify(payload)); + } + } catch (error) {} + } + + function emitConsentEvent(detail) { + if (!detail) { + return; + } + try { + var event = new CustomEvent("slimstat:consent:updated", { detail: detail }); + document.dispatchEvent(event); + } catch (err) { + try { + var fallback = document.createEvent("CustomEvent"); + fallback.initCustomEvent("slimstat:consent:updated", true, true, detail); + document.dispatchEvent(fallback); + } catch (compatError) { + /* ignore */ + } + } + } + + function maybeEmitConsentChange(detail) { + if (!detail) { + return; + } + var snapshot = detail.allowed + "|" + detail.mode + "|" + detail.reason; + if (snapshot !== lastConsentSnapshot) { + lastConsentSnapshot = snapshot; + // Only emit consent event if it's a meaningful change (not just initial check) + // This prevents duplicate pageview requests during initial load + var params = currentSlimStatParams(); + var hasPageviewId = params.id && parseInt(params.id, 10) > 0; + + // If we have a pageview ID, emit the event (consent changed after tracking) + // If we don't have an ID yet, the initial pageview will handle consent, so skip event + if (hasPageviewId) { + emitConsentEvent(detail); + } + } + } + + function slimstatConsentAllowed(params, options) { + options = options || {}; + var s = params || {}; + var gdprEnabled = s.gdpr_enabled !== "off"; + var anonMode = s.anonymous_tracking === "on"; + var setCookie = s.set_tracker_cookie === "on"; + var anonymizeIP = s.anonymize_ip === "on"; + var hashIP = s.hash_ip === "on"; + var integrationKey = s.consent_integration || ""; + var consentLevel = s.consent_level_integration || "statistics"; + + /* debug logging removed */ + + try { + var dntEnabled = s.respect_dnt === "on"; + if (dntEnabled && typeof navigator !== "undefined" && (navigator.doNotTrack === "1" || navigator.doNotTrack === "yes")) { + var blocked = { allowed: false, mode: "blocked", reason: "dnt" }; + maybeEmitConsentChange(blocked); + return blocked; + } + } catch (err) { + /* ignore */ + } + + // GDPR disabled: do not gate tracking behind CMP/consent checks. + // Keep anonymous mode behavior if explicitly enabled by admin settings. + if (!gdprEnabled) { + if (anonMode) { + var gdprOffAnon = { allowed: true, mode: "anonymous", reason: "gdpr_disabled_anonymous_mode" }; + maybeEmitConsentChange(gdprOffAnon); + return gdprOffAnon; + } + var gdprOff = { allowed: true, mode: "full", reason: "gdpr_disabled" }; + maybeEmitConsentChange(gdprOff); + return gdprOff; + } + + var collectsPII = !!(setCookie || (!anonymizeIP && !hashIP)); + var requiresCmpCheck = collectsPII || anonMode; + var cmpAllows = null; + + if (requiresCmpCheck) { + if (integrationKey === "wp_consent_api" || integrationKey === "wpconsent" || integrationKey === "wp_consent" || integrationKey === "") { + var jsConsent = detectWPConsentAPI(consentLevel); + if (jsConsent !== null) { + cmpAllows = jsConsent; + } + if (cmpAllows === null && s.server_side_consent !== undefined) { + cmpAllows = !!s.server_side_consent; + } + if (integrationKey === "" && cmpAllows === null) { + cmpAllows = true; + } + } + + if (cmpAllows === null && (integrationKey === "real_cookie_banner" || integrationKey === "rcb" || integrationKey === "realcookie")) { + var rcbConsent = detectRealCookieBannerConsent(consentLevel); + if (rcbConsent !== null) { + cmpAllows = rcbConsent; + } else { + if (options.isConsentRetry) { + var fallback = detectWPConsentAPI(consentLevel); + if (fallback !== null) { + cmpAllows = fallback; + } + } + } + } + + if (cmpAllows === null && (integrationKey === "slimstat_banner" || integrationKey === "slimstat")) { + var cookieName = s.gdpr_cookie_name || "slimstat_gdpr_consent"; + var bannerConsent = detectSlimStatBanner(cookieName, consentLevel); + if (bannerConsent !== null) { + cmpAllows = bannerConsent; + } + } + + if (cmpAllows === null) { + if (anonMode) { + cmpAllows = true; + } else if (collectsPII && integrationKey && integrationKey !== "") { + cmpAllows = false; + } else { + cmpAllows = true; + } + } else { + } + + // Only use previous consent upgrade if current consent is unknown (null) + // Do NOT override an explicit rejection (false) with a previous consent + if (cmpAllows !== true && hasConsentUpgradeSucceeded()) { + cmpAllows = true; + } + } + + if (anonMode) { + var cmpGranted = cmpAllows === true; + var anonDecision = { + allowed: true, + mode: cmpGranted ? "full" : "anonymous", + reason: cmpGranted ? "anonymous_mode_consented" : "anonymous_mode", + }; + maybeEmitConsentChange(anonDecision); + return anonDecision; + } + + if (!collectsPII) { + var noPii = { allowed: true, mode: "full", reason: "no_pii" }; + maybeEmitConsentChange(noPii); + return noPii; + } + + if (cmpAllows === false) { + var denied = { allowed: false, mode: "blocked", reason: "cmp_denied" }; + maybeEmitConsentChange(denied); + return denied; + } + + var allowedResult = { allowed: true, mode: "full", reason: "cmp_allowed" }; + maybeEmitConsentChange(allowedResult); + return allowedResult; + } function buildPageviewBase(params) { if (!isEmpty(params.id) && parseInt(params.id, 10) > 0) return "action=slimtrack&id=" + params.id; @@ -469,15 +1100,57 @@ function sendPageview(options) { options = options || {}; extractSlimStatParams(); + + // Prevent duplicate requests with stronger locking mechanism + var requestKey = "slimstat_pageview_" + (options.isNavigation ? "nav" : "init") + "_" + (options.isConsentRetry ? "retry" : "normal"); + if (window.sendingSlimStatPageview || window[requestKey]) { + return; + } + window.sendingSlimStatPageview = true; + window[requestKey] = true; + var params = currentSlimStatParams(); + var consentUpgradeParam = ""; + if (options.consentUpgrade) { + consentUpgradeParam = "&consent_upgrade=1"; + if (params.id) { + // Send the current pageview ID (with checksum) so the server can + // update this specific record, same as in the explicit upgrade AJAX. + consentUpgradeParam += "&pageview_id=" + encodeURIComponent(params.id); + } + } + + var consentDecision = slimstatConsentAllowed(params, { + isNavigation: !!options.isNavigation, + isConsentRetry: !!options.isConsentRetry, + }); + + if (!consentDecision.allowed) { + window.sendingSlimStatPageview = false; + delete window[requestKey]; + return; + } + + if (options.consentUpgrade && consentDecision.mode === "full") { + consentUpgradeParam = "&consent_upgrade=1"; + if (params.id) { + // Send the current pageview ID (with checksum) so the server can + // update this specific record, same as in the explicit upgrade AJAX. + consentUpgradeParam += "&pageview_id=" + encodeURIComponent(params.id); + } + } + // Check if this is a navigation event (not initial page load) var isNavigationEvent = options.isNavigation || false; + var isConsentRetry = options.isConsentRetry || false; // For navigation events, always track regardless of javascript_mode // For initial page load, skip if server-side tracking is active - if (!isNavigationEvent && !isEmpty(params.id) && parseInt(params.id, 10) > 0) { + if (!isNavigationEvent && !isConsentRetry && !isEmpty(params.id) && parseInt(params.id, 10) > 0) { // Server-side tracking is active for initial page load, skip pageview but allow interactions + window.sendingSlimStatPageview = false; + delete window[requestKey]; return; } @@ -488,16 +1161,25 @@ } var payloadBase = buildPageviewBase(params); - if (!payloadBase) return; + + if (!payloadBase) { + window.sendingSlimStatPageview = false; + delete window[requestKey]; + return; + } // Prevent duplicate pageview requests if (pageviewInProgress) { + window.sendingSlimStatPageview = false; + delete window[requestKey]; return; } // De-duplicate rapid navigations (e.g., WP Interactivity quick transitions) var now = Date.now(); if (payloadBase === lastPageviewPayload && now - lastPageviewSentAt < 150) { + window.sendingSlimStatPageview = false; + delete window[requestKey]; return; } @@ -507,73 +1189,141 @@ var useBeacon = !waitForId; // need sync response when creating id // Avoid parallel initial pageview duplication - if (inflightPageview && waitForId) return; + if (inflightPageview && waitForId) { + window.sendingSlimStatPageview = false; + delete window[requestKey]; + return; + } inflightPageview = waitForId; pageviewInProgress = true; // Reset finalization state when starting new pageview // Note: finalizationInProgress is now managed in initSlimStatRuntime scope - var run = function () { - Fingerprint2.get(FP_EXCLUDES, function (components) { - initFingerprintHash(components); - // Initial pageview (no id yet) should be immediate for faster id assignment - sendToServer(payloadBase + buildSlimStatData(components), useBeacon, { immediate: isEmpty(params.id) }); - showOptoutMessage(); + // Consolidated flag reset helper to prevent race conditions + var resetPageviewFlags = function () { + // Single source of truth for flag resets + // Delay allows sendToServer queue to process before allowing next pageview + setTimeout(function () { inflightPageview = false; pageviewInProgress = false; + window.sendingSlimStatPageview = false; + delete window[requestKey]; + }, 200); + }; - // Reset pageview state after successful completion - setTimeout(function () { - pageviewInProgress = false; - }, 100); - }); + var onComplete = function (success) { + if (options.consentUpgrade) { + handleConsentUpgradeResult(!!success); + } + resetPageviewFlags(); + }; + + // Add consent parameters if provided (from banner accept) + if (options.consent && (options.consent === "accepted" || options.consent === "denied")) { + consentUpgradeParam += "&banner_consent=" + encodeURIComponent(options.consent); + if (options.consentNonce) { + consentUpgradeParam += "&banner_consent_nonce=" + encodeURIComponent(options.consentNonce); + } + } + + var run = function () { + // If anonymous mode is active, skip fingerprinting entirely to ensure no PII is collected/sent + if (consentDecision.mode === "anonymous") { + initFingerprintHash(null); + sendToServer(payloadBase + buildSlimStatData({}) + consentUpgradeParam, useBeacon, { immediate: isEmpty(params.id), onComplete: onComplete }); + return; + } + + // FingerprintJS v4 async init; if it fails, proceed without fingerprint + try { + // Safely check if FingerprintJS library is available + var fpPromise = null; + if (typeof FingerprintJS !== "undefined" && FingerprintJS.load) { + fpPromise = FingerprintJS.load(); + } + + // Only proceed with promise chain if we have a valid promise + if (fpPromise && typeof fpPromise.then === "function") { + fpPromise + .then(function (result) { + initFingerprintHash(result); + sendToServer(payloadBase + buildSlimStatData(result.components || {}) + consentUpgradeParam, useBeacon, { immediate: isEmpty(params.id), onComplete: onComplete }); + }) + .catch(function () { + initFingerprintHash(null); + sendToServer(payloadBase + buildSlimStatData({}) + consentUpgradeParam, useBeacon, { immediate: isEmpty(params.id), onComplete: onComplete }); + }); + } else { + // Library not available; proceed without fingerprint + initFingerprintHash(null); + sendToServer(payloadBase + buildSlimStatData({}) + consentUpgradeParam, useBeacon, { immediate: isEmpty(params.id), onComplete: onComplete }); + } + } catch (e) { + // Catch synchronous errors (shouldn't happen, but defensive) + initFingerprintHash(null); + sendToServer(payloadBase + buildSlimStatData({}) + consentUpgradeParam, useBeacon, { immediate: isEmpty(params.id), onComplete: onComplete }); + } }; if (window.requestIdleCallback) window.requestIdleCallback(run); else setTimeout(run, 250); } - // -------------------------- Opt-out UI -------------------------- // - function showOptoutMessage() { - var params = currentSlimStatParams(); - var optCookies = params.oc ? params.oc.split(",") : []; - var show = optCookies.length > 0; - for (var i = 0; i < optCookies.length; i++) - if (getCookie(optCookies[i])) { - show = false; - break; - } - if (!show) return false; - var xhr; + // -------------------------- Consent Management -------------------------- // + // GDPR consent is now handled by external CMP plugins (Complianz, Cookie Notice, etc.) + // SlimStat integrates via WP Consent API or custom integrations + // No internal banner or consent UI is provided + + // -------------------------- Offline Data Handling -------------------------- // + function storeOffline(payload) { try { - xhr = new XMLHttpRequest(); + var offline = loadOfflineQueue(); + offline.push({ p: payload, t: Date.now() }); + saveOfflineQueue(offline); } catch (e) { - return false; + // Silently fail if localStorage is not available } - xhr.open("POST", params.ajaxurl, true); - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); - xhr.withCredentials = true; - xhr.onreadystatechange = function () { - if (xhr.readyState === 4 && xhr.status === 200) { - var div = document.createElement("div"); - div.innerHTML = xhr.responseText; - document.body.appendChild(div); - } - }; - xhr.send("action=slimstat_optout_html"); - return true; } - function optOut(event, cookieValue) { - event = event || window.event; - if (event && event.preventDefault) event.preventDefault(); - else if (event) event.returnValue = false; - var params = currentSlimStatParams(); - var expiration = new Date(Date.now() + 31536000000); // 1 year - document.cookie = "slimstat_optout_tracking=" + cookieValue + ";path=" + (params.baseurl || "/") + ";expires=" + expiration.toGMTString(); - var target = event.target || event.srcElement; - if (target && target.parentNode && target.parentNode.parentNode) target.parentNode.parentNode.removeChild(target.parentNode); + function flushOfflineQueue() { + try { + var offline = loadOfflineQueue(); + if (!offline.length) return; + + var params = currentSlimStatParams(); + if (!params.id || parseInt(params.id, 10) <= 0) return; // need valid ID to send + + // Send offline items in batches to avoid overwhelming the server + var batchSize = 5; + var sent = 0; + var toRemove = []; + + for (var i = 0; i < offline.length && sent < batchSize; i++) { + var item = offline[i]; + if (item && item.p) { + // Update payload with current ID if it has a placeholder + var payload = item.p; + if (payload.indexOf("id=pending") !== -1) { + payload = payload.replace("id=pending", "id=" + params.id); + } + + if (sendToServer(payload, false, { priority: "normal" })) { + toRemove.push(i); + sent++; + } + } + } + + // Remove sent items from offline queue + if (toRemove.length > 0) { + for (var j = toRemove.length - 1; j >= 0; j--) { + offline.splice(toRemove[j], 1); + } + saveOfflineQueue(offline); + } + } catch (e) { + // Silently fail if there are any issues + } } // -------------------------- Public API (legacy names preserved) -------------------------- // @@ -592,8 +1342,7 @@ base64_encode: base64Encode, get_page_performance: getPagePerformance, get_server_latency: getServerLatency, - optout: optOut, - show_optout_message: showOptoutMessage, + // Deprecated GDPR UI removed add_event: addEvent, in_array: anySubstring, empty: isEmpty, @@ -603,6 +1352,16 @@ init_fingerprint_hash: initFingerprintHash, get_slimstat_data: buildSlimStatData, get_component_value: getComponentValue, + // Offline data handling + store_offline: storeOffline, + flush_offline_queue: flushOfflineQueue, + consent: { + checkAllowed: slimstatConsentAllowed, + emit: emitConsentEvent, + normalize: normalizeConsent, + sendChange: sendConsentChangeToServer, + }, + requestConsentUpgrade: requestConsentUpgrade, // New internal helpers (not documented previously) _extract_params: extractSlimStatParams, _send_pageview: sendPageview, @@ -617,6 +1376,9 @@ }; })(); +// Expose SlimStat to the global scope so it remains accessible after esbuild bundling +window.SlimStat = SlimStat; + // Polyfills for ES5 and older browsers if (!Element.prototype.matches) { Element.prototype.matches = @@ -657,6 +1419,27 @@ var OFFLINE_KEY = "slimstat_offline_queue"; var pageviewInProgress = false; + // Helper functions for consent detection (local copies for scope access) + function isFunction(value) { + return typeof value === "function"; + } + + function isObject(value) { + return value !== null && typeof value === "object"; + } + + function getCookieStrict(name) { + if (!name) return null; + try { + var safeName = name.replace(/([.$?*|{}()\[\]\\\/\+^])/g, "\\$1"); + var pattern = "(?:^|;)\\s*" + safeName + "=([^;]*)"; + var match = document.cookie.match(pattern); + return match ? decodeURIComponent(match[1]) : null; + } catch (e) { + return null; + } + } + function loadOfflineQueue() { try { var raw = localStorage.getItem(OFFLINE_KEY); @@ -691,8 +1474,16 @@ pageviewInProgress: pageviewInProgress, }); + var requestConsentUpgrade = + SlimStat.requestConsentUpgrade || + function () { + return false; + }; + // Track whether we've already finalized the current pageview (avoid duplicate beacons) var finalizedPageviews = {}; + // Track currently in-flight finalization requests to avoid races + var inFlightFinalizations = {}; // Finalization state management (moved from SlimStat closure to avoid scope issues) var finalizationInProgress = false; var lastFinalizationReason = ""; @@ -704,10 +1495,17 @@ } catch (e) { /* ignore */ } + // Global flag to prevent concurrent pageview sends + try { + if (typeof window.sendingSlimStatPageview === "undefined") window.sendingSlimStatPageview = false; + if (typeof window.slimstatPageviewTracked === "undefined") window.slimstatPageviewTracked = false; + } catch (e) { + /* ignore */ + } function finalizeCurrent(reason) { - var p = window.SlimStatParams || {}; - if (!p.id || parseInt(p.id, 10) <= 0 || finalizedPageviews[p.id]) return; // no pageview id yet or already finalized + var p = currentSlimStatParams(); + if (!p.id || parseInt(p.id, 10) <= 0 || finalizedPageviews[p.id] || inFlightFinalizations[p.id]) return; // no pageview id yet or already finalized/in-flight var now = Date.now(); if (finalizationInProgress || (reason === lastFinalizationReason && now - lastFinalizationTime < FINALIZATION_COOLDOWN)) return; @@ -716,24 +1514,30 @@ lastFinalizationReason = reason; lastFinalizationTime = now; + // Mark in-flight to prevent concurrent senders (race protection) + inFlightFinalizations[p.id] = true; + // Old behavior: send a simple finalize to let the server compute dt_out var payload = "action=slimtrack&id=" + p.id + (reason ? "&fv=" + encodeURIComponent(reason) : ""); SlimStat.send_to_server(payload, true, { priority: "high", immediate: false }); + + // Mark finalized and clear in-flight after a short window finalizedPageviews[p.id] = true; setTimeout(function () { + delete inFlightFinalizations[p.id]; finalizationInProgress = false; }, 120); } // Observe for parameter mutations (meta tag or script changes) // Only observe if we don't have an ID yet (to avoid unnecessary tracking requests) - var lastParams = JSON.stringify(window.SlimStatParams || {}); + var lastParams = JSON.stringify(currentSlimStatParams()); var observer = new MutationObserver(function () { - var params = window.SlimStatParams || {}; + var params = currentSlimStatParams(); // Only extract params if we don't have an ID yet (initial page load) if (SlimStat.empty(params.id) || parseInt(params.id, 10) <= 0) { SlimStat._extract_params(); - var serialized = JSON.stringify(window.SlimStatParams || {}); + var serialized = JSON.stringify(currentSlimStatParams()); if (serialized !== lastParams) lastParams = serialized; // reserved for future diff-based logic } }); @@ -743,34 +1547,294 @@ // Initial pageview SlimStat.add_event(window, "load", function () { SlimStat._extract_params(); + + // Proceed with normal tracking; consent is gated by CMP checks in sendPageview() SlimStat._send_pageview(); + // Flush any offline stored payloads after initial pageview queued setTimeout(function () { try { - if (navigator.onLine !== false) typeof flushOfflineQueue === "function" && flushOfflineQueue(); + if (navigator.onLine !== false) SlimStat.flush_offline_queue(); } catch (e) {} }, 500); }); + // Listen for WP Consent API consent changes and retry pageview if previously blocked + document.addEventListener("wp_listen_for_consent_change", function (event) { + try { + var detail = (event && event.detail) || {}; + var params = currentSlimStatParams(); + var selectedCategory = params.consent_level_integration || "statistics"; + var retryKey = "slimstatConsentRetried_" + selectedCategory; + + if (detail[selectedCategory] && detail[selectedCategory] === "allow" && (!window[retryKey] || window[retryKey] === false)) { + window[retryKey] = true; + SlimStat._send_pageview({ + consentUpgrade: true, + }); + } + } catch (e) { + /* ignore */ + } + }); + + // Backwards compatibility: some integrations expose a helper on window + if (typeof window.wp_listen_for_consent_change === "function") { + try { + window.wp_listen_for_consent_change(function (category) { + var params = currentSlimStatParams(); + var selectedCategory = params.consent_level_integration || "statistics"; + var retryKey = "slimstatConsentRetried_" + selectedCategory; + + if (category === selectedCategory && (!window[retryKey] || window[retryKey] === false)) { + window[retryKey] = true; + SlimStat._send_pageview({ + consentUpgrade: true, + }); + } + }); + } catch (e) { + /* ignore */ + } + } + + // Listen for consent type definitions to catch late initializations + document.addEventListener("wp_consent_type_defined", function () { + try { + var params = currentSlimStatParams(); + var selectedCategory = params.consent_level_integration || "statistics"; + var retryKey = "slimstatConsentRetried_" + selectedCategory; + + if (!window[retryKey]) { + if (typeof window.wp_has_consent === "function") { + if (window.wp_has_consent(selectedCategory)) { + window[retryKey] = true; + SlimStat._send_pageview({ + consentUpgrade: true, + }); + } + } else { + window[retryKey] = true; + SlimStat._send_pageview({ + consentUpgrade: true, + }); + } + } + } catch (e) { + /* ignore */ + } + }); + + // Standard WP Consent API event listener + document.addEventListener("wp_consent_change", function (event) { + if (event.detail && event.detail.category) { + var category = event.detail.category; + var params = currentSlimStatParams(); + var selectedCategory = params.consent_level_integration || "statistics"; + + // Use category-specific retry flag to prevent race conditions between CMPs + var retryKey = "slimstatConsentRetried_" + selectedCategory; + var consentRetried = window[retryKey] || false; + + var shouldTrack = !consentRetried && category === selectedCategory && (!params.id || parseInt(params.id, 10) <= 0); + + if (shouldTrack) { + // Double-check with WP Consent API if available + if (typeof window.wp_has_consent === "function" && !window.wp_has_consent(selectedCategory)) return; + window[retryKey] = true; + SlimStat._send_pageview({ + consentUpgrade: true, + }); + } + + // Send consent change to server via REST API + if (category === selectedCategory) { + try { + var hasConsent = false; + if (typeof window.wp_has_consent === "function") { + hasConsent = window.wp_has_consent(selectedCategory); + } else if (event.detail.consent !== undefined) { + hasConsent = event.detail.consent === true || event.detail.consent === "allow"; + } + + // Clear consent upgrade state when consent is denied + if (!hasConsent) { + markConsentUpgradeDone(false); + } + + var parsedConsent = normalizeConsent({ + statistics: hasConsent ? "allow" : "deny", + }); + + var pageviewId = null; + if (params.id && parseInt(params.id, 10) > 0) { + pageviewId = parseInt(params.id, 10); + } + + sendConsentChangeToServer("wp_consent_api", parsedConsent, pageviewId); + } catch (consentError) {} + } + } + }); + + // CMP-specific listeners + // Define tryTrackIfAllowed in outer scope so consent helpers can access it + function tryTrackIfAllowed(extraOptions) { + var params = currentSlimStatParams(); + var selectedCategory = params.consent_level_integration || "statistics"; + var integrationKey = params.consent_integration || ""; + + if (typeof window.wp_has_consent === "function") { + try { + var hasConsent = window.wp_has_consent(selectedCategory); + if (!hasConsent) { + return; + } + } catch (err) { + return; + } + } + + if (integrationKey === "real_cookie_banner" || integrationKey === "rcb" || integrationKey === "realcookie") { + var rcbConsent = detectRealCookieBannerConsent(selectedCategory); + if (rcbConsent === false) { + return; + } + } + + requestConsentUpgrade(extraOptions || {}); + } + + // CMP-specific listeners + (function registerCmpListeners() { + // Complianz: enable specific category + document.addEventListener("cmplz_enable_category", function (e) { + var params = currentSlimStatParams(); + var selectedCategory = params.consent_level_integration || "statistics"; + var cat = (e && e.detail && (e.detail.category || e.detail)) || ""; + if (cat === selectedCategory) tryTrackIfAllowed(); + }); + + // Complianz: status event (allow/deny) + document.addEventListener("cmplz_event_status", function (e) { + var params = currentSlimStatParams(); + var selectedCategory = params.consent_level_integration || "statistics"; + var d = (e && e.detail) || {}; + var cat = d.category || d.type || ""; + var allowed = d.status === "allow" || d.enabled === true; + if (cat === selectedCategory && allowed) tryTrackIfAllowed(); + }); + + // Real Cookie Banner - multiple event names for compatibility + var rcbHandlerDebounceTimer = null; + var rcbHandlerLastCall = 0; + function handleRCBConsentChange(e) { + var now = Date.now(); + var params = currentSlimStatParams(); + var integrationKey = params.consent_integration || ""; + + if (integrationKey !== "real_cookie_banner" && integrationKey !== "rcb" && integrationKey !== "realcookie") { + return; + } + + var selectedCategory = params.consent_level_integration || "statistics"; + var ok = false; + var consentData = null; + + if (e && e.detail) { + if (e.detail.consent && selectedCategory in e.detail.consent) { + var categoryConsent = e.detail.consent[selectedCategory]; + if (typeof categoryConsent === "boolean") { + ok = categoryConsent; + consentData = e.detail.consent; + } else if (categoryConsent && categoryConsent.cookie !== null) { + ok = true; + consentData = e.detail.consent; + } + } else if (e.detail.button && (e.detail.button === "accept_all" || e.detail.button === "accept_essentials" || e.detail.button === "save")) { + var consentCheck = SlimStat.consent.checkAllowed(params, {}); + ok = consentCheck && consentCheck.allowed && consentCheck.mode === "full"; + if (e.detail.consent) { + consentData = e.detail.consent; + } + } + } + + if (!ok && typeof window.wp_has_consent === "function") { + ok = !!window.wp_has_consent(selectedCategory); + } + + // Send consent change to server via REST API + try { + var parsedConsent = normalizeConsent(consentData || { statistics: ok }); + var pageviewId = null; + if (params.id && parseInt(params.id, 10) > 0) { + pageviewId = parseInt(params.id, 10); + } + sendConsentChangeToServer("real_cookie_banner", parsedConsent, pageviewId); + } catch (rcbError) {} + + if (!ok) { + var consentCheck = SlimStat.consent.checkAllowed(params, {}); + ok = consentCheck && consentCheck.allowed && consentCheck.mode === "full"; + } + + if (ok) { + clearTimeout(rcbHandlerDebounceTimer); + var timeSinceLastCall = now - rcbHandlerLastCall; + var debounceDelay = timeSinceLastCall < 100 ? 100 - timeSinceLastCall : 0; + rcbHandlerDebounceTimer = setTimeout(function () { + // Rely on tryTrackIfAllowed so the new consent upgrade flow runs uniformly + var params = currentSlimStatParams(); + if (!params.id || parseInt(params.id, 10) <= 0) { + tryTrackIfAllowed(); + } else { + SlimStat.requestConsentUpgrade(); + } + }, debounceDelay); + rcbHandlerLastCall = now; + } + } + + // Listen for all RCB event variations + document.addEventListener("RealCookieBannerConsentChanged", handleRCBConsentChange); + document.addEventListener("rcb-consent-changed", handleRCBConsentChange); + document.addEventListener("rcb-consent-update", handleRCBConsentChange); + document.addEventListener("rcb-consent-saved", handleRCBConsentChange); + + // CookieYes (cookie-law-info) events + // Fire after a short delay to allow WP Consent API state to update + document.addEventListener("cookieyes_consent_update", function () { + setTimeout(tryTrackIfAllowed, 50); + }); + document.addEventListener("cookieyes_preferences_update", function () { + setTimeout(tryTrackIfAllowed, 50); + }); + // Older CookieYes/CLI plugins + document.addEventListener("cli_consent_update", function () { + setTimeout(tryTrackIfAllowed, 50); + }); + })(); + // Before unload finalize if we have an active id // Use multiple lifecycle signals to improve reliability across SPA / tab discard / mobile browsers SlimStat.add_event(document, "visibilitychange", function () { // Only finalize if we have an active ID and the page is actually hidden - var params = window.SlimStatParams || {}; + var params = currentSlimStatParams(); if (document.visibilityState === "hidden" && params.id && parseInt(params.id, 10) > 0) { debouncedFinalize("visibility"); } }); SlimStat.add_event(window, "pagehide", function () { // Only finalize if we have an active ID - var params = window.SlimStatParams || {}; + var params = currentSlimStatParams(); if (params.id && parseInt(params.id, 10) > 0) { debouncedFinalize("pagehide"); } }); SlimStat.add_event(window, "beforeunload", function () { // Only finalize if we have an active ID - var params = window.SlimStatParams || {}; + var params = currentSlimStatParams(); if (params.id && parseInt(params.id, 10) > 0) { debouncedFinalize("beforeunload"); } @@ -780,7 +1844,7 @@ var finalizationTimeout = null; function debouncedFinalize(reason) { // Don't finalize if already finalized for this pageview ID - var p = window.SlimStatParams || {}; + var p = currentSlimStatParams(); if (!p.id || finalizedPageviews[p.id]) return; if (finalizationTimeout) { @@ -793,7 +1857,7 @@ // Online event to resend offline queue SlimStat.add_event(window, "online", function () { - flushOfflineQueue(); + SlimStat.flush_offline_queue(); flushPendingInteractions(); }); @@ -819,6 +1883,10 @@ SlimStat.add_event(document.body, "click", function (e) { var target = e.target; while (target && target !== document.body) { + // Skip GDPR consent buttons to avoid duplicate processing + if (target.hasAttribute && target.hasAttribute("data-consent")) { + break; + } if (target.matches && target.matches("a,button,input,area")) { SlimStat.ss_track(e, null, null); break; @@ -826,6 +1894,8 @@ target = target.parentNode; } }); + + // No GDPR consent buttons; managed by CMPs } function setupNavigationHooks() { @@ -918,4 +1988,223 @@ // Setup interaction tracking setupClickDelegation(); setupNavigationHooks(); + + /** + * Setup Consent Upgrade Handler + * + * Listens for consent events from various CMPs (Consent Management Platforms) + * and upgrades anonymous tracking to full PII tracking when consent is granted. + * + * Flow: + * 1. User visits site → Anonymous tracking (hashed IP, no cookies) + * 2. User grants consent → Consent event fired + * 3. AJAX request sent to upgrade existing pageview record + * 4. IP hash replaced with real IP, tracking cookie set + */ + function setupConsentUpgradeHandler() { + var legacyEvents = ["RCB/OptIn", "RCB/OptIn/All", "cookieyes_consent_update", "cookieyes_preferences_update", "cli_consent_update", "wp_listen_load", "wp_consent_type_functional", "wp_consent_type_statistics", "slimstat_banner_consent"]; + + legacyEvents.forEach(function (eventName) { + document.addEventListener(eventName, function (e) { + requestConsentUpgrade(e); + }); + }); + + document.addEventListener("slimstat:consent:updated", function (event) { + if (event && event.detail && event.detail.allowed && event.detail.mode === "full") { + requestConsentUpgrade(); + } + }); + + SlimStat.requestConsentUpgrade = requestConsentUpgrade; + } + + function initSlimStatBanner() { + var bannerInitialized = false; + + function attachBannerHandlers() { + if (bannerInitialized) { + return; + } + + var params = currentSlimStatParams(); + if (!params || params.use_slimstat_banner !== "on") { + return; + } + + var banner = document.getElementById("slimstat-gdpr-banner"); + if (!banner) { + return; + } + + bannerInitialized = true; + + setTimeout(function () { + if (banner && banner.classList) { + banner.classList.add("show"); + } else if (banner) { + banner.style.display = "block"; + } + }, 50); + + var buttons = banner.querySelectorAll("[data-consent]"); + for (var i = 0; i < buttons.length; i++) { + (function (button) { + if (button.addEventListener) { + button.addEventListener( + "click", + function (event) { + if (event && typeof event.preventDefault === "function") { + event.preventDefault(); + } + if (event && typeof event.stopPropagation === "function") { + event.stopPropagation(); + } + var consent = button.getAttribute("data-consent") || ""; + submitBannerDecision(consent, banner); + }, + false + ); + } else if (button.attachEvent) { + button.attachEvent("onclick", function (event) { + if (event && typeof event.preventDefault === "function") { + event.preventDefault(); + } + if (event && typeof event.stopPropagation === "function") { + event.stopPropagation(); + } + var consent = button.getAttribute("data-consent") || ""; + submitBannerDecision(consent, banner); + }); + } else { + button.onclick = function (event) { + if (event && typeof event.preventDefault === "function") { + event.preventDefault(); + } + if (event && typeof event.stopPropagation === "function") { + event.stopPropagation(); + } + var consent = button.getAttribute("data-consent") || ""; + submitBannerDecision(consent, banner); + }; + } + })(buttons[i]); + } + } + + function submitBannerDecision(consent, bannerEl) { + if (!consent || (consent !== "accepted" && consent !== "denied")) { + return; + } + + var params = currentSlimStatParams(); + var nonce = params.wp_rest_nonce || ""; + var cookieName = params.gdpr_cookie_name || "slimstat_gdpr_consent"; + var cookiePath = params.gdpr_cookie_path || params.baseurl || "/"; + + // Set cookie immediately + try { + var expiry = new Date(); + expiry.setTime(expiry.getTime() + 365 * 24 * 60 * 60 * 1000); + var cookie = cookieName + "=" + consent + "; path=" + cookiePath + "; expires=" + expiry.toUTCString() + "; SameSite=Lax"; + if (window && window.location && window.location.protocol === "https:") { + cookie += "; Secure"; + } + document.cookie = cookie; + } catch (cookieError) { + /* ignore cookie errors */ + } + + // Close banner with animation (before request) + if (bannerEl && bannerEl.classList) { + bannerEl.classList.remove("show"); + bannerEl.classList.add("hiding"); + } else if (bannerEl) { + // Fallback for browsers without classList + bannerEl.style.transition = "transform 0.3s ease-out, opacity 0.3s ease-out"; + bannerEl.style.transform = "translateY(100%)"; + bannerEl.style.opacity = "0"; + } + + // Remove banner from DOM after animation completes + setTimeout(function () { + if (bannerEl && bannerEl.parentNode) { + bannerEl.parentNode.removeChild(bannerEl); + } + }, 350); + + // Dispatch consent event immediately + if (consent === "accepted") { + try { + if (typeof CustomEvent === "function") { + document.dispatchEvent(new CustomEvent("slimstat_banner_consent", { detail: { consent: consent } })); + } else { + var evt = document.createEvent("Event"); + evt.initEvent("slimstat_banner_consent", true, true); + document.dispatchEvent(evt); + } + } catch (dispatchError) { + /* ignore */ + } + + // Send consent change to server via REST API + try { + var parsedConsent = normalizeConsent(consent); + var pageviewId = null; + if (params.id && parseInt(params.id, 10) > 0) { + pageviewId = parseInt(params.id, 10); + } + sendConsentChangeToServer("slimstat_banner", parsedConsent, pageviewId); + } catch (apiError) {} + + try { + requestConsentUpgrade({ consent: consent, consentNonce: nonce }); + } catch (sendError) {} + } else if (consent === "denied") { + // Send consent change to server via REST API + try { + var parsedConsentDenied = normalizeConsent(consent); + sendConsentChangeToServer("slimstat_banner", parsedConsentDenied, null); + } catch (apiError) {} + + // Call revocation handler to delete tracking cookie + try { + var ajaxUrl = params.ajaxurl || "/wp-admin/admin-ajax.php"; + var revokeXhr = new XMLHttpRequest(); + revokeXhr.open("POST", ajaxUrl, true); + revokeXhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + revokeXhr.send("action=slimstat_consent_revoked&nonce=" + encodeURIComponent(nonce)); + revokeXhr.onload = function () {}; + revokeXhr.onerror = function () {}; + } catch (revokeError) { + /* ignore */ + } + } + } + + if (document.readyState && document.readyState !== "loading") { + attachBannerHandlers(); + } + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", attachBannerHandlers, false); + window.addEventListener("load", attachBannerHandlers, false); + } else if (document.attachEvent) { + document.attachEvent("onreadystatechange", function () { + if (document.readyState === "complete") { + attachBannerHandlers(); + } + }); + window.attachEvent("onload", attachBannerHandlers); + } else { + if (document.readyState === "complete") { + attachBannerHandlers(); + } + window.onload = attachBannerHandlers; + } + } + + // Initialize consent helpers + initSlimStatBanner(); + setupConsentUpgradeHandler(); })(); @@ -1,2 +1,10 @@ -(()=>{var st=Object.create;var We=Object.defineProperty;var ut=Object.getOwnPropertyDescriptor;var lt=Object.getOwnPropertyNames;var ft=Object.getPrototypeOf,ct=Object.prototype.hasOwnProperty;var dt=(p,g)=>()=>(g||p((g={exports:{}}).exports,g),g.exports);var gt=(p,g,C,x)=>{if(g&&typeof g=="object"||typeof g=="function")for(let v of lt(g))!ct.call(p,v)&&v!==C&&We(p,v,{get:()=>g[v],enumerable:!(x=ut(g,v))||x.enumerable});return p};var ht=(p,g,C)=>(C=p!=null?st(ft(p)):{},gt(g||!p||!p.__esModule?We(C,"default",{value:p,enumerable:!0}):C,p));var ze=dt((je,Pe)=>{(function(p,g,C){"use strict";typeof window<"u"&&typeof define=="function"&&define.amd?define(C):typeof Pe<"u"&&Pe.exports?Pe.exports=C():g.exports?g.exports=C():g[p]=C()})("Fingerprint2",je,function(){"use strict";typeof Array.isArray>"u"&&(Array.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"});var p=function(e,t){e=[e[0]>>>16,e[0]&65535,e[1]>>>16,e[1]&65535],t=[t[0]>>>16,t[0]&65535,t[1]>>>16,t[1]&65535];var r=[0,0,0,0];return r[3]+=e[3]+t[3],r[2]+=r[3]>>>16,r[3]&=65535,r[2]+=e[2]+t[2],r[1]+=r[2]>>>16,r[2]&=65535,r[1]+=e[1]+t[1],r[0]+=r[1]>>>16,r[1]&=65535,r[0]+=e[0]+t[0],r[0]&=65535,[r[0]<<16|r[1],r[2]<<16|r[3]]},g=function(e,t){e=[e[0]>>>16,e[0]&65535,e[1]>>>16,e[1]&65535],t=[t[0]>>>16,t[0]&65535,t[1]>>>16,t[1]&65535];var r=[0,0,0,0];return r[3]+=e[3]*t[3],r[2]+=r[3]>>>16,r[3]&=65535,r[2]+=e[2]*t[3],r[1]+=r[2]>>>16,r[2]&=65535,r[2]+=e[3]*t[2],r[1]+=r[2]>>>16,r[2]&=65535,r[1]+=e[1]*t[3],r[0]+=r[1]>>>16,r[1]&=65535,r[1]+=e[2]*t[2],r[0]+=r[1]>>>16,r[1]&=65535,r[1]+=e[3]*t[1],r[0]+=r[1]>>>16,r[1]&=65535,r[0]+=e[0]*t[3]+e[1]*t[2]+e[2]*t[1]+e[3]*t[0],r[0]&=65535,[r[0]<<16|r[1],r[2]<<16|r[3]]},C=function(e,t){return t%=64,t===32?[e[1],e[0]]:t<32?[e[0]<<t|e[1]>>>32-t,e[1]<<t|e[0]>>>32-t]:(t-=32,[e[1]<<t|e[0]>>>32-t,e[0]<<t|e[1]>>>32-t])},x=function(e,t){return t%=64,t===0?e:t<32?[e[0]<<t|e[1]>>>32-t,e[1]<<t]:[e[1]<<t-32,0]},v=function(e,t){return[e[0]^t[0],e[1]^t[1]]},P=function(e){return e=v(e,[0,e[0]>>>1]),e=g(e,[4283543511,3981806797]),e=v(e,[0,e[0]>>>1]),e=g(e,[3301882366,444984403]),e=v(e,[0,e[0]>>>1]),e},F=function(e,t){e=e||"",t=t||0;for(var r=e.length%16,a=e.length-r,i=[0,t],o=[0,t],l=[0,0],c=[0,0],A=[2277735313,289559509],M=[1291169091,658871167],h=0;h<a;h=h+16)l=[e.charCodeAt(h+4)&255|(e.charCodeAt(h+5)&255)<<8|(e.charCodeAt(h+6)&255)<<16|(e.charCodeAt(h+7)&255)<<24,e.charCodeAt(h)&255|(e.charCodeAt(h+1)&255)<<8|(e.charCodeAt(h+2)&255)<<16|(e.charCodeAt(h+3)&255)<<24],c=[e.charCodeAt(h+12)&255|(e.charCodeAt(h+13)&255)<<8|(e.charCodeAt(h+14)&255)<<16|(e.charCodeAt(h+15)&255)<<24,e.charCodeAt(h+8)&255|(e.charCodeAt(h+9)&255)<<8|(e.charCodeAt(h+10)&255)<<16|(e.charCodeAt(h+11)&255)<<24],l=g(l,A),l=C(l,31),l=g(l,M),i=v(i,l),i=C(i,27),i=p(i,o),i=p(g(i,[0,5]),[0,1390208809]),c=g(c,M),c=C(c,33),c=g(c,A),o=v(o,c),o=C(o,31),o=p(o,i),o=p(g(o,[0,5]),[0,944331445]);switch(l=[0,0],c=[0,0],r){case 15:c=v(c,x([0,e.charCodeAt(h+14)],48));case 14:c=v(c,x([0,e.charCodeAt(h+13)],40));case 13:c=v(c,x([0,e.charCodeAt(h+12)],32));case 12:c=v(c,x([0,e.charCodeAt(h+11)],24));case 11:c=v(c,x([0,e.charCodeAt(h+10)],16));case 10:c=v(c,x([0,e.charCodeAt(h+9)],8));case 9:c=v(c,[0,e.charCodeAt(h+8)]),c=g(c,M),c=C(c,33),c=g(c,A),o=v(o,c);case 8:l=v(l,x([0,e.charCodeAt(h+7)],56));case 7:l=v(l,x([0,e.charCodeAt(h+6)],48));case 6:l=v(l,x([0,e.charCodeAt(h+5)],40));case 5:l=v(l,x([0,e.charCodeAt(h+4)],32));case 4:l=v(l,x([0,e.charCodeAt(h+3)],24));case 3:l=v(l,x([0,e.charCodeAt(h+2)],16));case 2:l=v(l,x([0,e.charCodeAt(h+1)],8));case 1:l=v(l,[0,e.charCodeAt(h)]),l=g(l,A),l=C(l,31),l=g(l,M),i=v(i,l)}return i=v(i,[0,e.length]),o=v(o,[0,e.length]),i=p(i,o),o=p(o,i),i=P(i),o=P(o),i=p(i,o),o=p(o,i),("00000000"+(i[0]>>>0).toString(16)).slice(-8)+("00000000"+(i[1]>>>0).toString(16)).slice(-8)+("00000000"+(o[0]>>>0).toString(16)).slice(-8)+("00000000"+(o[1]>>>0).toString(16)).slice(-8)},te={preprocessor:null,audio:{timeout:1e3,excludeIOS11:!0},fonts:{swfContainerId:"fingerprintjs2",swfPath:"flash/compiled/FontList.swf",userDefinedFonts:[],extendedJsFonts:!1},screen:{detectScreenOrientation:!0},plugins:{sortPluginsFor:[/palemoon/i],excludeIE:!1},extraComponents:[],excludes:{enumerateDevices:!0,pixelRatio:!0,doNotTrack:!0,fontsFlash:!0,adBlock:!0},NOT_AVAILABLE:"not available",ERROR:"error",EXCLUDED:"excluded"},G=function(e,t){if(Array.prototype.forEach&&e.forEach===Array.prototype.forEach)e.forEach(t);else if(e.length===+e.length)for(var r=0,a=e.length;r<a;r++)t(e[r],r,e);else for(var i in e)e.hasOwnProperty(i)&&t(e[i],i,e)},K=function(e,t){var r=[];return e==null?r:Array.prototype.map&&e.map===Array.prototype.map?e.map(t):(G(e,function(a,i,o){r.push(t(a,i,o))}),r)},re=function(e,t){if(t==null)return e;var r,a;for(a in t)r=t[a],r!=null&&!Object.prototype.hasOwnProperty.call(e,a)&&(e[a]=r);return e},pe=function(e,t){if(!W())return e(t.NOT_AVAILABLE);navigator.mediaDevices.enumerateDevices().then(function(r){e(r.map(function(a){return"id="+a.deviceId+";gid="+a.groupId+";"+a.kind+";"+a.label}))}).catch(function(r){e(r)})},W=function(){return navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices},ue=function(e,t){var r=t.audio;if(r.excludeIOS11&&navigator.userAgent.match(/OS 11.+Version\/11.+Safari/))return e(t.EXCLUDED);var a=window.OfflineAudioContext||window.webkitOfflineAudioContext;if(a==null)return e(t.NOT_AVAILABLE);var i=new a(1,44100,44100),o=i.createOscillator();o.type="triangle",o.frequency.setValueAtTime(1e4,i.currentTime);var l=i.createDynamicsCompressor();G([["threshold",-50],["knee",40],["ratio",12],["reduction",-20],["attack",0],["release",.25]],function(A){l[A[0]]!==void 0&&typeof l[A[0]].setValueAtTime=="function"&&l[A[0]].setValueAtTime(A[1],i.currentTime)}),o.connect(l),l.connect(i.destination),o.start(0),i.startRendering();var c=setTimeout(function(){return console.warn('Audio fingerprint timed out. Please report bug at https://github.com/fingerprintjs/fingerprintjs with your user agent: "'+navigator.userAgent+'".'),i.oncomplete=function(){},i=null,e("audioTimeout")},r.timeout);i.oncomplete=function(A){var M;try{clearTimeout(c),M=A.renderedBuffer.getChannelData(0).slice(4500,5e3).reduce(function(h,$){return h+Math.abs($)},0).toString(),o.disconnect(),l.disconnect()}catch(h){e(h);return}e(M)}},le=function(e){e(navigator.userAgent)},D=function(e,t){e(navigator.webdriver==null?t.NOT_AVAILABLE:navigator.webdriver)},U=function(e,t){e(navigator.language||navigator.userLanguage||navigator.browserLanguage||navigator.systemLanguage||t.NOT_AVAILABLE)},me=function(e,t){e(window.screen.colorDepth||t.NOT_AVAILABLE)},we=function(e,t){e(navigator.deviceMemory||t.NOT_AVAILABLE)},m=function(e,t){e(window.devicePixelRatio||t.NOT_AVAILABLE)},S=function(e,t){e(L(t))},L=function(e){var t=[window.screen.width,window.screen.height];return e.screen.detectScreenOrientation&&t.sort().reverse(),t},T=function(e,t){e(z(t))},z=function(e){if(window.screen.availWidth&&window.screen.availHeight){var t=[window.screen.availHeight,window.screen.availWidth];return e.screen.detectScreenOrientation&&t.sort().reverse(),t}return e.NOT_AVAILABLE},J=function(e){e(new Date().getTimezoneOffset())},ae=function(e,t){if(window.Intl&&window.Intl.DateTimeFormat){e(new window.Intl.DateTimeFormat().resolvedOptions().timeZone||t.NOT_AVAILABLE);return}e(t.NOT_AVAILABLE)},q=function(e,t){e(H(t))},fe=function(e,t){e(j(t))},ne=function(e,t){e(Q(t))},ce=function(e){e(!!window.HTMLElement.prototype.addBehavior)},ie=function(e){e(!!window.openDatabase)},Te=function(e,t){e(V(t))},de=function(e,t){e(k(t))},ye=function(e,t){e(I(t))},_e=function(e,t){if(He()){e(Se(t));return}e(t.NOT_AVAILABLE)},Ie=function(e,t){if(Ve()){e(Ce());return}e(t.NOT_AVAILABLE)},Me=function(e){if(Ve()){e(xe());return}e()},Le=function(e){e(ge())},Ne=function(e){e(Ee())},Re=function(e){e(E())},Ae=function(e){e(qe())},De=function(e){e(Qe())},n=function(e,t){if(!Ze())return e("swf object not loaded");if(!$e())return e("flash not installed");if(!t.fonts.swfPath)return e("missing options.fonts.swfPath");tt(function(r){e(r)},t)},s=function(e,t){var r=["monospace","sans-serif","serif"],a=["Andale Mono","Arial","Arial Black","Arial Hebrew","Arial MT","Arial Narrow","Arial Rounded MT Bold","Arial Unicode MS","Bitstream Vera Sans Mono","Book Antiqua","Bookman Old Style","Calibri","Cambria","Cambria Math","Century","Century Gothic","Century Schoolbook","Comic Sans","Comic Sans MS","Consolas","Courier","Courier New","Geneva","Georgia","Helvetica","Helvetica Neue","Impact","Lucida Bright","Lucida Calligraphy","Lucida Console","Lucida Fax","LUCIDA GRANDE","Lucida Handwriting","Lucida Sans","Lucida Sans Typewriter","Lucida Sans Unicode","Microsoft Sans Serif","Monaco","Monotype Corsiva","MS Gothic","MS Outlook","MS PGothic","MS Reference Sans Serif","MS Sans Serif","MS Serif","MYRIAD","MYRIAD PRO","Palatino","Palatino Linotype","Segoe Print","Segoe Script","Segoe UI","Segoe UI Light","Segoe UI Semibold","Segoe UI Symbol","Tahoma","Times","Times New Roman","Times New Roman PS","Trebuchet MS","Verdana","Wingdings","Wingdings 2","Wingdings 3"];if(t.fonts.extendedJsFonts){var i=["Abadi MT Condensed Light","Academy Engraved LET","ADOBE CASLON PRO","Adobe Garamond","ADOBE GARAMOND PRO","Agency FB","Aharoni","Albertus Extra Bold","Albertus Medium","Algerian","Amazone BT","American Typewriter","American Typewriter Condensed","AmerType Md BT","Andalus","Angsana New","AngsanaUPC","Antique Olive","Aparajita","Apple Chancery","Apple Color Emoji","Apple SD Gothic Neo","Arabic Typesetting","ARCHER","ARNO PRO","Arrus BT","Aurora Cn BT","AvantGarde Bk BT","AvantGarde Md BT","AVENIR","Ayuthaya","Bandy","Bangla Sangam MN","Bank Gothic","BankGothic Md BT","Baskerville","Baskerville Old Face","Batang","BatangChe","Bauer Bodoni","Bauhaus 93","Bazooka","Bell MT","Bembo","Benguiat Bk BT","Berlin Sans FB","Berlin Sans FB Demi","Bernard MT Condensed","BernhardFashion BT","BernhardMod BT","Big Caslon","BinnerD","Blackadder ITC","BlairMdITC TT","Bodoni 72","Bodoni 72 Oldstyle","Bodoni 72 Smallcaps","Bodoni MT","Bodoni MT Black","Bodoni MT Condensed","Bodoni MT Poster Compressed","Bookshelf Symbol 7","Boulder","Bradley Hand","Bradley Hand ITC","Bremen Bd BT","Britannic Bold","Broadway","Browallia New","BrowalliaUPC","Brush Script MT","Californian FB","Calisto MT","Calligrapher","Candara","CaslonOpnface BT","Castellar","Centaur","Cezanne","CG Omega","CG Times","Chalkboard","Chalkboard SE","Chalkduster","Charlesworth","Charter Bd BT","Charter BT","Chaucer","ChelthmITC Bk BT","Chiller","Clarendon","Clarendon Condensed","CloisterBlack BT","Cochin","Colonna MT","Constantia","Cooper Black","Copperplate","Copperplate Gothic","Copperplate Gothic Bold","Copperplate Gothic Light","CopperplGoth Bd BT","Corbel","Cordia New","CordiaUPC","Cornerstone","Coronet","Cuckoo","Curlz MT","DaunPenh","Dauphin","David","DB LCD Temp","DELICIOUS","Denmark","DFKai-SB","Didot","DilleniaUPC","DIN","DokChampa","Dotum","DotumChe","Ebrima","Edwardian Script ITC","Elephant","English 111 Vivace BT","Engravers MT","EngraversGothic BT","Eras Bold ITC","Eras Demi ITC","Eras Light ITC","Eras Medium ITC","EucrosiaUPC","Euphemia","Euphemia UCAS","EUROSTILE","Exotc350 Bd BT","FangSong","Felix Titling","Fixedsys","FONTIN","Footlight MT Light","Forte","FrankRuehl","Fransiscan","Freefrm721 Blk BT","FreesiaUPC","Freestyle Script","French Script MT","FrnkGothITC Bk BT","Fruitger","FRUTIGER","Futura","Futura Bk BT","Futura Lt BT","Futura Md BT","Futura ZBlk BT","FuturaBlack BT","Gabriola","Galliard BT","Gautami","Geeza Pro","Geometr231 BT","Geometr231 Hv BT","Geometr231 Lt BT","GeoSlab 703 Lt BT","GeoSlab 703 XBd BT","Gigi","Gill Sans","Gill Sans MT","Gill Sans MT Condensed","Gill Sans MT Ext Condensed Bold","Gill Sans Ultra Bold","Gill Sans Ultra Bold Condensed","Gisha","Gloucester MT Extra Condensed","GOTHAM","GOTHAM BOLD","Goudy Old Style","Goudy Stout","GoudyHandtooled BT","GoudyOLSt BT","Gujarati Sangam MN","Gulim","GulimChe","Gungsuh","GungsuhChe","Gurmukhi MN","Haettenschweiler","Harlow Solid Italic","Harrington","Heather","Heiti SC","Heiti TC","HELV","Herald","High Tower Text","Hiragino Kaku Gothic ProN","Hiragino Mincho ProN","Hoefler Text","Humanst 521 Cn BT","Humanst521 BT","Humanst521 Lt BT","Imprint MT Shadow","Incised901 Bd BT","Incised901 BT","Incised901 Lt BT","INCONSOLATA","Informal Roman","Informal011 BT","INTERSTATE","IrisUPC","Iskoola Pota","JasmineUPC","Jazz LET","Jenson","Jester","Jokerman","Juice ITC","Kabel Bk BT","Kabel Ult BT","Kailasa","KaiTi","Kalinga","Kannada Sangam MN","Kartika","Kaufmann Bd BT","Kaufmann BT","Khmer UI","KodchiangUPC","Kokila","Korinna BT","Kristen ITC","Krungthep","Kunstler Script","Lao UI","Latha","Leelawadee","Letter Gothic","Levenim MT","LilyUPC","Lithograph","Lithograph Light","Long Island","Lydian BT","Magneto","Maiandra GD","Malayalam Sangam MN","Malgun Gothic","Mangal","Marigold","Marion","Marker Felt","Market","Marlett","Matisse ITC","Matura MT Script Capitals","Meiryo","Meiryo UI","Microsoft Himalaya","Microsoft JhengHei","Microsoft New Tai Lue","Microsoft PhagsPa","Microsoft Tai Le","Microsoft Uighur","Microsoft YaHei","Microsoft Yi Baiti","MingLiU","MingLiU_HKSCS","MingLiU_HKSCS-ExtB","MingLiU-ExtB","Minion","Minion Pro","Miriam","Miriam Fixed","Mistral","Modern","Modern No. 20","Mona Lisa Solid ITC TT","Mongolian Baiti","MONO","MoolBoran","Mrs Eaves","MS LineDraw","MS Mincho","MS PMincho","MS Reference Specialty","MS UI Gothic","MT Extra","MUSEO","MV Boli","Nadeem","Narkisim","NEVIS","News Gothic","News GothicMT","NewsGoth BT","Niagara Engraved","Niagara Solid","Noteworthy","NSimSun","Nyala","OCR A Extended","Old Century","Old English Text MT","Onyx","Onyx BT","OPTIMA","Oriya Sangam MN","OSAKA","OzHandicraft BT","Palace Script MT","Papyrus","Parchment","Party LET","Pegasus","Perpetua","Perpetua Titling MT","PetitaBold","Pickwick","Plantagenet Cherokee","Playbill","PMingLiU","PMingLiU-ExtB","Poor Richard","Poster","PosterBodoni BT","PRINCETOWN LET","Pristina","PTBarnum BT","Pythagoras","Raavi","Rage Italic","Ravie","Ribbon131 Bd BT","Rockwell","Rockwell Condensed","Rockwell Extra Bold","Rod","Roman","Sakkal Majalla","Santa Fe LET","Savoye LET","Sceptre","Script","Script MT Bold","SCRIPTINA","Serifa","Serifa BT","Serifa Th BT","ShelleyVolante BT","Sherwood","Shonar Bangla","Showcard Gothic","Shruti","Signboard","SILKSCREEN","SimHei","Simplified Arabic","Simplified Arabic Fixed","SimSun","SimSun-ExtB","Sinhala Sangam MN","Sketch Rockwell","Skia","Small Fonts","Snap ITC","Snell Roundhand","Socket","Souvenir Lt BT","Staccato222 BT","Steamer","Stencil","Storybook","Styllo","Subway","Swis721 BlkEx BT","Swiss911 XCm BT","Sylfaen","Synchro LET","System","Tamil Sangam MN","Technical","Teletype","Telugu Sangam MN","Tempus Sans ITC","Terminal","Thonburi","Traditional Arabic","Trajan","TRAJAN PRO","Tristan","Tubular","Tunga","Tw Cen MT","Tw Cen MT Condensed","Tw Cen MT Condensed Extra Bold","TypoUpright BT","Unicorn","Univers","Univers CE 55 Medium","Univers Condensed","Utsaah","Vagabond","Vani","Vijaya","Viner Hand ITC","VisualUI","Vivaldi","Vladimir Script","Vrinda","Westminster","WHITNEY","Wide Latin","ZapfEllipt BT","ZapfHumnst BT","ZapfHumnst Dm BT","Zapfino","Zurich BlkEx BT","Zurich Ex BT","ZWAdobeF"];a=a.concat(i)}a=a.concat(t.fonts.userDefinedFonts),a=a.filter(function(y,N){return a.indexOf(y)===N});var o="mmmmmmmmmmlli",l="72px",c=document.getElementsByTagName("body")[0],A=document.createElement("div"),M=document.createElement("div"),h={},$={},R=function(){var y=document.createElement("span");return y.style.position="absolute",y.style.left="-9999px",y.style.fontSize=l,y.style.fontStyle="normal",y.style.fontWeight="normal",y.style.letterSpacing="normal",y.style.lineBreak="auto",y.style.lineHeight="normal",y.style.textTransform="none",y.style.textAlign="left",y.style.textDecoration="none",y.style.textShadow="none",y.style.whiteSpace="normal",y.style.wordBreak="normal",y.style.wordSpacing="normal",y.innerHTML=o,y},oe=function(y,N){var X=R();return X.style.fontFamily="'"+y+"',"+N,X},ee=function(){for(var y=[],N=0,X=r.length;N<X;N++){var ve=R();ve.style.fontFamily=r[N],A.appendChild(ve),y.push(ve)}return y},se=function(){for(var y={},N=0,X=a.length;N<X;N++){for(var ve=[],Ge=0,ot=r.length;Ge<ot;Ge++){var Ke=oe(a[N],r[Ge]);M.appendChild(Ke),ve.push(Ke)}y[a[N]]=ve}return y},Fe=function(y){for(var N=!1,X=0;X<r.length;X++)if(N=y[X].offsetWidth!==h[r[X]]||y[X].offsetHeight!==$[r[X]],N)return N;return N},Oe=ee();c.appendChild(A);for(var he=0,at=r.length;he<at;he++)h[r[he]]=Oe[he].offsetWidth,$[r[he]]=Oe[he].offsetHeight;var nt=se();c.appendChild(M);for(var Xe=[],be=0,it=a.length;be<it;be++)Fe(nt[a[be]])&&Xe.push(a[be]);c.removeChild(M),c.removeChild(A),e(Xe)},u=function(e,t){Je()?t.plugins.excludeIE?e(t.EXCLUDED):e(w(t)):e(f(t))},f=function(e){if(navigator.plugins==null)return e.NOT_AVAILABLE;for(var t=[],r=0,a=navigator.plugins.length;r<a;r++)navigator.plugins[r]&&t.push(navigator.plugins[r]);return d(e)&&(t=t.sort(function(i,o){return i.name>o.name?1:i.name<o.name?-1:0})),K(t,function(i){var o=K(i,function(l){return[l.type,l.suffixes]});return[i.name,i.description,o]})},w=function(e){var t=[];if(Object.getOwnPropertyDescriptor&&Object.getOwnPropertyDescriptor(window,"ActiveXObject")||"ActiveXObject"in window){var r=["AcroPDF.PDF","Adodb.Stream","AgControl.AgControl","DevalVRXCtrl.DevalVRXCtrl.1","MacromediaFlashPaper.MacromediaFlashPaper","Msxml2.DOMDocument","Msxml2.XMLHTTP","PDF.PdfCtrl","QuickTime.QuickTime","QuickTimeCheckObject.QuickTimeCheck.1","RealPlayer","RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)","RealVideo.RealVideo(tm) ActiveX Control (32-bit)","Scripting.Dictionary","SWCtl.SWCtl","Shell.UIHelper","ShockwaveFlash.ShockwaveFlash","Skype.Detection","TDCCtl.TDCCtl","WMPlayer.OCX","rmocx.RealPlayer G2 Control","rmocx.RealPlayer G2 Control.1"];t=K(r,function(a){try{return new window.ActiveXObject(a),a}catch{return e.ERROR}})}else t.push(e.NOT_AVAILABLE);return navigator.plugins&&(t=t.concat(f(e))),t},d=function(e){for(var t=!1,r=0,a=e.plugins.sortPluginsFor.length;r<a;r++){var i=e.plugins.sortPluginsFor[r];if(navigator.userAgent.match(i)){t=!0;break}}return t},B=function(e){e(Y())},_=function(e,t){e(O(t))},H=function(e){try{return!!window.sessionStorage}catch{return e.ERROR}},j=function(e){try{return!!window.localStorage}catch{return e.ERROR}},Q=function(e){if(Ye())return e.EXCLUDED;try{return!!window.indexedDB}catch{return e.ERROR}},O=function(e){return navigator.hardwareConcurrency?navigator.hardwareConcurrency:e.NOT_AVAILABLE},V=function(e){return navigator.cpuClass||e.NOT_AVAILABLE},k=function(e){return navigator.platform?navigator.platform:e.NOT_AVAILABLE},I=function(e){return navigator.doNotTrack?navigator.doNotTrack:navigator.msDoNotTrack?navigator.msDoNotTrack:window.doNotTrack?window.doNotTrack:e.NOT_AVAILABLE},Y=function(){var e=0,t;typeof navigator.maxTouchPoints<"u"?e=navigator.maxTouchPoints:typeof navigator.msMaxTouchPoints<"u"&&(e=navigator.msMaxTouchPoints);try{document.createEvent("TouchEvent"),t=!0}catch{t=!1}var r="ontouchstart"in window;return[e,t,r]},Se=function(e){var t=[],r=document.createElement("canvas");r.width=2e3,r.height=200,r.style.display="inline";var a=r.getContext("2d");return a.rect(0,0,10,10),a.rect(2,2,6,6),t.push("canvas winding:"+(a.isPointInPath(5,5,"evenodd")===!1?"yes":"no")),a.textBaseline="alphabetic",a.fillStyle="#f60",a.fillRect(125,1,62,20),a.fillStyle="#069",e.dontUseFakeFontInCanvas?a.font="11pt Arial":a.font="11pt no-real-font-123",a.fillText("Cwm fjordbank glyphs vext quiz, \u{1F603}",2,15),a.fillStyle="rgba(102, 204, 0, 0.2)",a.font="18pt Arial",a.fillText("Cwm fjordbank glyphs vext quiz, \u{1F603}",4,45),a.globalCompositeOperation="multiply",a.fillStyle="rgb(255,0,255)",a.beginPath(),a.arc(50,50,50,0,Math.PI*2,!0),a.closePath(),a.fill(),a.fillStyle="rgb(0,255,255)",a.beginPath(),a.arc(100,50,50,0,Math.PI*2,!0),a.closePath(),a.fill(),a.fillStyle="rgb(255,255,0)",a.beginPath(),a.arc(75,100,50,0,Math.PI*2,!0),a.closePath(),a.fill(),a.fillStyle="rgb(255,0,255)",a.arc(75,75,75,0,Math.PI*2,!0),a.arc(75,75,25,0,Math.PI*2,!0),a.fill("evenodd"),r.toDataURL&&t.push("canvas fp:"+r.toDataURL()),t},Ce=function(){var e,t=function(R){return e.clearColor(0,0,0,1),e.enable(e.DEPTH_TEST),e.depthFunc(e.LEQUAL),e.clear(e.COLOR_BUFFER_BIT|e.DEPTH_BUFFER_BIT),"["+R[0]+", "+R[1]+"]"},r=function(R){var oe=R.getExtension("EXT_texture_filter_anisotropic")||R.getExtension("WEBKIT_EXT_texture_filter_anisotropic")||R.getExtension("MOZ_EXT_texture_filter_anisotropic");if(oe){var ee=R.getParameter(oe.MAX_TEXTURE_MAX_ANISOTROPY_EXT);return ee===0&&(ee=2),ee}else return null};if(e=ke(),!e)return null;var a=[],i="attribute vec2 attrVertex;varying vec2 varyinTexCoordinate;uniform vec2 uniformOffset;void main(){varyinTexCoordinate=attrVertex+uniformOffset;gl_Position=vec4(attrVertex,0,1);}",o="precision mediump float;varying vec2 varyinTexCoordinate;void main() {gl_FragColor=vec4(varyinTexCoordinate,0,1);}",l=e.createBuffer();e.bindBuffer(e.ARRAY_BUFFER,l);var c=new Float32Array([-.2,-.9,0,.4,-.26,0,0,.732134444,0]);e.bufferData(e.ARRAY_BUFFER,c,e.STATIC_DRAW),l.itemSize=3,l.numItems=3;var A=e.createProgram(),M=e.createShader(e.VERTEX_SHADER);e.shaderSource(M,i),e.compileShader(M);var h=e.createShader(e.FRAGMENT_SHADER);e.shaderSource(h,o),e.compileShader(h),e.attachShader(A,M),e.attachShader(A,h),e.linkProgram(A),e.useProgram(A),A.vertexPosAttrib=e.getAttribLocation(A,"attrVertex"),A.offsetUniform=e.getUniformLocation(A,"uniformOffset"),e.enableVertexAttribArray(A.vertexPosArray),e.vertexAttribPointer(A.vertexPosAttrib,l.itemSize,e.FLOAT,!1,0,0),e.uniform2f(A.offsetUniform,1,1),e.drawArrays(e.TRIANGLE_STRIP,0,l.numItems);try{a.push(e.canvas.toDataURL())}catch{}a.push("extensions:"+(e.getSupportedExtensions()||[]).join(";")),a.push("webgl aliased line width range:"+t(e.getParameter(e.ALIASED_LINE_WIDTH_RANGE))),a.push("webgl aliased point size range:"+t(e.getParameter(e.ALIASED_POINT_SIZE_RANGE))),a.push("webgl alpha bits:"+e.getParameter(e.ALPHA_BITS)),a.push("webgl antialiasing:"+(e.getContextAttributes().antialias?"yes":"no")),a.push("webgl blue bits:"+e.getParameter(e.BLUE_BITS)),a.push("webgl depth bits:"+e.getParameter(e.DEPTH_BITS)),a.push("webgl green bits:"+e.getParameter(e.GREEN_BITS)),a.push("webgl max anisotropy:"+r(e)),a.push("webgl max combined texture image units:"+e.getParameter(e.MAX_COMBINED_TEXTURE_IMAGE_UNITS)),a.push("webgl max cube map texture size:"+e.getParameter(e.MAX_CUBE_MAP_TEXTURE_SIZE)),a.push("webgl max fragment uniform vectors:"+e.getParameter(e.MAX_FRAGMENT_UNIFORM_VECTORS)),a.push("webgl max render buffer size:"+e.getParameter(e.MAX_RENDERBUFFER_SIZE)),a.push("webgl max texture image units:"+e.getParameter(e.MAX_TEXTURE_IMAGE_UNITS)),a.push("webgl max texture size:"+e.getParameter(e.MAX_TEXTURE_SIZE)),a.push("webgl max varying vectors:"+e.getParameter(e.MAX_VARYING_VECTORS)),a.push("webgl max vertex attribs:"+e.getParameter(e.MAX_VERTEX_ATTRIBS)),a.push("webgl max vertex texture image units:"+e.getParameter(e.MAX_VERTEX_TEXTURE_IMAGE_UNITS)),a.push("webgl max vertex uniform vectors:"+e.getParameter(e.MAX_VERTEX_UNIFORM_VECTORS)),a.push("webgl max viewport dims:"+t(e.getParameter(e.MAX_VIEWPORT_DIMS))),a.push("webgl red bits:"+e.getParameter(e.RED_BITS)),a.push("webgl renderer:"+e.getParameter(e.RENDERER)),a.push("webgl shading language version:"+e.getParameter(e.SHADING_LANGUAGE_VERSION)),a.push("webgl stencil bits:"+e.getParameter(e.STENCIL_BITS)),a.push("webgl vendor:"+e.getParameter(e.VENDOR)),a.push("webgl version:"+e.getParameter(e.VERSION));try{var $=e.getExtension("WEBGL_debug_renderer_info");$&&(a.push("webgl unmasked vendor:"+e.getParameter($.UNMASKED_VENDOR_WEBGL)),a.push("webgl unmasked renderer:"+e.getParameter($.UNMASKED_RENDERER_WEBGL)))}catch{}return e.getShaderPrecisionFormat?(G(["FLOAT","INT"],function(R){G(["VERTEX","FRAGMENT"],function(oe){G(["HIGH","MEDIUM","LOW"],function(ee){G(["precision","rangeMin","rangeMax"],function(se){var Fe=e.getShaderPrecisionFormat(e[oe+"_SHADER"],e[ee+"_"+R])[se];se!=="precision"&&(se="precision "+se);var Oe=["webgl ",oe.toLowerCase()," shader ",ee.toLowerCase()," ",R.toLowerCase()," ",se,":",Fe].join("");a.push(Oe)})})})}),Be(e),a):(Be(e),a)},xe=function(){try{var e=ke(),t=e.getExtension("WEBGL_debug_renderer_info"),r=e.getParameter(t.UNMASKED_VENDOR_WEBGL)+"~"+e.getParameter(t.UNMASKED_RENDERER_WEBGL);return Be(e),r}catch{return null}},ge=function(){var e=document.createElement("div");e.innerHTML=" ",e.className="adsbox";var t=!1;try{document.body.appendChild(e),t=document.getElementsByClassName("adsbox")[0].offsetHeight===0,document.body.removeChild(e)}catch{t=!1}return t},Ee=function(){if(typeof navigator.languages<"u")try{var e=navigator.languages[0].substr(0,2);if(e!==navigator.language.substr(0,2))return!0}catch{return!0}return!1},E=function(){return window.screen.width<window.screen.availWidth||window.screen.height<window.screen.availHeight},qe=function(){var e=navigator.userAgent.toLowerCase(),t=navigator.oscpu,r=navigator.platform.toLowerCase(),a;e.indexOf("windows phone")>=0?a="Windows Phone":e.indexOf("windows")>=0||e.indexOf("win16")>=0||e.indexOf("win32")>=0||e.indexOf("win64")>=0||e.indexOf("win95")>=0||e.indexOf("win98")>=0||e.indexOf("winnt")>=0||e.indexOf("wow64")>=0?a="Windows":e.indexOf("android")>=0?a="Android":e.indexOf("linux")>=0||e.indexOf("cros")>=0||e.indexOf("x11")>=0?a="Linux":e.indexOf("iphone")>=0||e.indexOf("ipad")>=0||e.indexOf("ipod")>=0||e.indexOf("crios")>=0||e.indexOf("fxios")>=0?a="iOS":e.indexOf("macintosh")>=0||e.indexOf("mac_powerpc)")>=0?a="Mac":a="Other";var i="ontouchstart"in window||navigator.maxTouchPoints>0||navigator.msMaxTouchPoints>0;if(i&&a!=="Windows"&&a!=="Windows Phone"&&a!=="Android"&&a!=="iOS"&&a!=="Other"&&e.indexOf("cros")===-1)return!0;if(typeof t<"u"){if(t=t.toLowerCase(),t.indexOf("win")>=0&&a!=="Windows"&&a!=="Windows Phone")return!0;if(t.indexOf("linux")>=0&&a!=="Linux"&&a!=="Android")return!0;if(t.indexOf("mac")>=0&&a!=="Mac"&&a!=="iOS")return!0;if((t.indexOf("win")===-1&&t.indexOf("linux")===-1&&t.indexOf("mac")===-1)!=(a==="Other"))return!0}if(r.indexOf("win")>=0&&a!=="Windows"&&a!=="Windows Phone")return!0;if((r.indexOf("linux")>=0||r.indexOf("android")>=0||r.indexOf("pike")>=0)&&a!=="Linux"&&a!=="Android")return!0;if((r.indexOf("mac")>=0||r.indexOf("ipad")>=0||r.indexOf("ipod")>=0||r.indexOf("iphone")>=0)&&a!=="Mac"&&a!=="iOS")return!0;if(r.indexOf("arm")>=0&&a==="Windows Phone")return!1;if(r.indexOf("pike")>=0&&e.indexOf("opera mini")>=0)return!1;var o=r.indexOf("win")<0&&r.indexOf("linux")<0&&r.indexOf("mac")<0&&r.indexOf("iphone")<0&&r.indexOf("ipad")<0&&r.indexOf("ipod")<0;return o!==(a==="Other")?!0:typeof navigator.plugins>"u"&&a!=="Windows"&&a!=="Windows Phone"},Qe=function(){var e=navigator.userAgent.toLowerCase(),t=navigator.productSub,r;if(e.indexOf("edge/")>=0||e.indexOf("iemobile/")>=0)return!1;if(e.indexOf("opera mini")>=0)return!1;if(e.indexOf("firefox/")>=0?r="Firefox":e.indexOf("opera/")>=0||e.indexOf(" opr/")>=0?r="Opera":e.indexOf("chrome/")>=0?r="Chrome":e.indexOf("safari/")>=0?e.indexOf("android 1.")>=0||e.indexOf("android 2.")>=0||e.indexOf("android 3.")>=0||e.indexOf("android 4.")>=0?r="AOSP":r="Safari":e.indexOf("trident/")>=0?r="Internet Explorer":r="Other",(r==="Chrome"||r==="Safari"||r==="Opera")&&t!=="20030107")return!0;var a=eval.toString().length;if(a===37&&r!=="Safari"&&r!=="Firefox"&&r!=="Other")return!0;if(a===39&&r!=="Internet Explorer"&&r!=="Other")return!0;if(a===33&&r!=="Chrome"&&r!=="AOSP"&&r!=="Opera"&&r!=="Other")return!0;var i;try{throw"a"}catch(o){try{o.toSource(),i=!0}catch{i=!1}}return i&&r!=="Firefox"&&r!=="Other"},He=function(){var e=document.createElement("canvas");return!!(e.getContext&&e.getContext("2d"))},Ve=function(){if(!He())return!1;var e=ke(),t=!!window.WebGLRenderingContext&&!!e;return Be(e),t},Je=function(){return navigator.appName==="Microsoft Internet Explorer"?!0:!!(navigator.appName==="Netscape"&&/Trident/.test(navigator.userAgent))},Ye=function(){return("msWriteProfilerMark"in window)+("msLaunchUri"in navigator)+("msSaveBlob"in navigator)>=2},Ze=function(){return typeof window.swfobject<"u"},$e=function(){return window.swfobject.hasFlashPlayerVersion("9.0.0")},et=function(e){var t=document.createElement("div");t.setAttribute("id",e.fonts.swfContainerId),document.body.appendChild(t)},tt=function(e,t){var r="___fp_swf_loaded";window[r]=function(l){e(l)};var a=t.fonts.swfContainerId;et();var i={onReady:r},o={allowScriptAccess:"always",menu:"false"};window.swfobject.embedSWF(t.fonts.swfPath,a,"1","1","9.0.0",!1,i,o,{})},ke=function(){var e=document.createElement("canvas"),t=null;try{t=e.getContext("webgl")||e.getContext("experimental-webgl")}catch{}return t||(t=null),t},Be=function(e){var t=e.getExtension("WEBGL_lose_context");t?.loseContext()},rt=[{key:"userAgent",getData:le},{key:"webdriver",getData:D},{key:"language",getData:U},{key:"colorDepth",getData:me},{key:"deviceMemory",getData:we},{key:"pixelRatio",getData:m},{key:"hardwareConcurrency",getData:_},{key:"screenResolution",getData:S},{key:"availableScreenResolution",getData:T},{key:"timezoneOffset",getData:J},{key:"timezone",getData:ae},{key:"sessionStorage",getData:q},{key:"localStorage",getData:fe},{key:"indexedDb",getData:ne},{key:"addBehavior",getData:ce},{key:"openDatabase",getData:ie},{key:"cpuClass",getData:Te},{key:"platform",getData:de},{key:"doNotTrack",getData:ye},{key:"plugins",getData:u},{key:"canvas",getData:_e},{key:"webgl",getData:Ie},{key:"webglVendorAndRenderer",getData:Me},{key:"adBlock",getData:Le},{key:"hasLiedLanguages",getData:Ne},{key:"hasLiedResolution",getData:Re},{key:"hasLiedOs",getData:Ae},{key:"hasLiedBrowser",getData:De},{key:"touchSupport",getData:B},{key:"fonts",getData:s,pauseBefore:!0},{key:"fontsFlash",getData:n,pauseBefore:!0},{key:"audio",getData:ue},{key:"enumerateDevices",getData:pe}],Z=function(e){throw new Error("'new Fingerprint()' is deprecated, see https://github.com/fingerprintjs/fingerprintjs#upgrade-guide-from-182-to-200")};return Z.get=function(e,t){t?e||(e={}):(t=e,e={}),re(e,te),e.components=e.extraComponents.concat(rt);var r={data:[],addPreprocessedComponent:function(o,l){typeof e.preprocessor=="function"&&(l=e.preprocessor(o,l)),r.data.push({key:o,value:l})}},a=-1,i=function(o){if(a+=1,a>=e.components.length){t(r.data);return}var l=e.components[a];if(e.excludes[l.key]){i(!1);return}if(!o&&l.pauseBefore){a-=1,setTimeout(function(){i(!0)},1);return}try{l.getData(function(c){r.addPreprocessedComponent(l.key,c),i(!1)},e)}catch(c){r.addPreprocessedComponent(l.key,String(c)),i(!1)}};i(!1)},Z.getPromise=function(e){return new Promise(function(t,r){Z.get(e,t)})},Z.getV18=function(e,t){return t==null&&(t=e,e={}),Z.get(e,function(r){for(var a=[],i=0;i<r.length;i++){var o=r[i];if(o.value===(e.NOT_AVAILABLE||"not available"))a.push({key:o.key,value:"unknown"});else if(o.key==="plugins")a.push({key:"plugins",value:K(o.value,function(c){var A=K(c[2],function(M){return M.join?M.join("~"):M}).join(",");return[c[0],c[1],A].join("::")})});else if(["canvas","webgl"].indexOf(o.key)!==-1&&Array.isArray(o.value))a.push({key:o.key,value:o.value.join("~")});else if(["sessionStorage","localStorage","indexedDb","addBehavior","openDatabase"].indexOf(o.key)!==-1)if(o.value)a.push({key:o.key,value:1});else continue;else o.value?a.push(o.value.join?{key:o.key,value:o.value.join(";")}:o):a.push({key:o.key,value:o.value})}var l=F(K(a,function(c){return c.value}).join("~~~"),31);t(l,a)})},Z.x64hash128=F,Z.VERSION="2.1.4",Z})});var Ue=ht(ze()),b=function(){var p="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-",g="",C="",x=0,v=!1,P=[],F=!1,te=4,G=80,K="",re=0,pe=20,W,ue,le,D,U;function me(n){W.length>=pe&&W.shift(),W.push(n)}function we(){if(W.length){var n=D();if(!(!n.id||parseInt(n.id,10)<=0))for(;W.length;){var s=W.shift(),u="action=slimtrack&id="+n.id+s;de(u,!0,{priority:"normal"})}}}var m="slimstat_offline_queue";function S(n){n=(n||"").replace(/\r\n/g,` -`);for(var s="",u=0;u<n.length;u++){var f=n.charCodeAt(u);f<128?s+=String.fromCharCode(f):f<2048?s+=String.fromCharCode(f>>6|192,f&63|128):s+=String.fromCharCode(f>>12|224,f>>6&63|128,f&63|128)}return s}function L(n){var s="",u=0;for(n=S(n);u<n.length;){var f=n.charCodeAt(u++),w=n.charCodeAt(u++),d=n.charCodeAt(u++),B=f>>2,_=(f&3)<<4|w>>4,H=(w&15)<<2|d>>6,j=d&63;isNaN(w)?H=j=64:isNaN(d)&&(j=64),s+=p.charAt(B)+p.charAt(_)+p.charAt(H)+p.charAt(j)}return s}function T(n){if(n==null)return!0;var s=typeof n;return s==="boolean"?!n:s==="number"?isNaN(n)||n===0:s==="string"||Array.isArray(n)?n.length===0:s==="object"?Object.keys(n).length===0:!1}function z(n,s){if(!n||!s||!s.length)return!1;for(var u=0;u<s.length;u++)if(n.indexOf(s[u].trim())!==-1)return!0;return!1}function J(n){var s="; "+document.cookie,u=s.split("; "+n+"=");return u.length===2?u.pop().split(";").shift():""}function ae(n,s,u){n&&(n.addEventListener?n.addEventListener(s,u,!1):n.attachEvent?n.attachEvent("on"+s,u):n["on"+s]=u)}function q(){var n=(window.performance||{}).timing||{};return!n.responseEnd||!n.connectEnd?0:n.responseEnd-n.connectEnd}function fe(){var n=(window.performance||{}).timing||{};return!n.loadEventEnd||!n.responseEnd?0:n.loadEventEnd-n.responseEnd}function ne(n,s,u){for(var f=0;f<n.length;f++)if(n[f].key===s)return n[f].value;return u}function ce(){var n=document.querySelector('meta[name="slimstat-params"]');if(n)try{window.SlimStatParams=JSON.parse(n.getAttribute("content"))||{}}catch{}else for(var s=document.querySelectorAll("script"),u=s.length-1;u>=0;u--){var f=s[u].textContent.match(/var\s+SlimStatParams\s*=\s*({[\s\S]*?});/);if(f)try{window.SlimStatParams=new Function("return "+f[1])()||{};break}catch{}}return D()}function ie(n){try{var s=n.map(function(u){return u.value});g=Ue.default.x64hash128(s.join(""),31)}catch{g=""}}function Te(n){var s=ne(n,"screenResolution",[0,0]);return"&sw="+s[0]+"&sh="+s[1]+"&bw="+window.innerWidth+"&bh="+window.innerHeight+"&sl="+q()+"&pp="+fe()+"&fh="+g+"&tz="+ne(n,"timezoneOffset",0)}function de(n,s,u){if(T(n))return!1;u=u||{};var f={payload:n,useBeacon:s,opts:u,attempts:0},w=P.some(function(B){return B.payload===n});if(w)return!1;if(P.length>G)for(var d=P.length-1;d>=0&&P.length>G;d--)P[d].opts.priority!=="high"&&P.splice(d,1);return u.immediate||u.priority==="high"?(!P.length||P[0].payload!==n)&&P.unshift(f):P.push(f),F||ye(),!0}function ye(){if(!(F||!P.length)){var n=P.shift();if(n){F=!0;var s=function(u){if(!u&&n)if(n.attempts=(n.attempts||0)+1,n.attempts<te){var f=500*Math.pow(2,n.attempts);setTimeout(function(){P.unshift(n)},f)}else storeOffline(n.payload);F=!1,setTimeout(ye,50)};_e(n,s)}}}function _e(n,s){var u=D(),f=n.payload,w=n.useBeacon,d=["rest","ajax","adblock"],B={rest:u.ajaxurl_rest,ajax:u.ajaxurl_ajax,adblock:u.ajaxurl_adblock},_=u.transport,H=[_].concat(d.filter(function(O){return O!==_}));function j(O,V,k){k=k||{useNonce:!0};var I;try{I=new XMLHttpRequest}catch{return V&&V(),!1}I.open("POST",O,!0),I.setRequestHeader("Content-type","application/x-www-form-urlencoded"),I.setRequestHeader("X-Requested-With","XMLHttpRequest"),k.useNonce&&u.wp_rest_nonce&&I.setRequestHeader("X-WP-Nonce",u.wp_rest_nonce),I.withCredentials=!0,I.onreadystatechange=function(){if(I.readyState===4){if(I.status===403&&k.useNonce&&u.wp_rest_nonce){j(O,V,{useNonce:!1});return}if(I.status===200){var Y=parseInt(I.responseText,10);!isNaN(Y)&&Y>0&&(u.id=I.responseText,we()),s(!0)}else V&&V()}};try{I.send(f)}catch{V&&V()}return!0}function Q(O){if(O>=H.length)return s(!1),!1;var V=H[O],k=B[V];if(!k)return Q(O+1);if(w&&navigator.sendBeacon&&O===0){var I=navigator.sendBeacon(k,f);return I?(s(!0),!0):Q(O+1)}return j(k,function(){Q(O+1)},{useNonce:!0})}Q(0)}function Ie(n,s,u){var f=D();if(T(f.id)||isNaN(parseInt(f.id,10))||parseInt(f.id,10)<=0){try{var w=Me(n,s);me(w)}catch{}return!1}if(!n||T(n.type)||n.type==="focus")return!1;u=typeof u=="boolean"?u:!0;var d=n.target||n.srcElement;if(!d)return!1;var B={};T(s)||(B.note=s);var _="";if(function(){if(d.nodeName){var Ee=d.nodeName.toLowerCase();if(Ee==="input"||Ee==="button"){for(var E=d.parentNode;E&&E.nodeName&&E.nodeName.toLowerCase()!=="form";)E=E.parentNode;E&&E.action&&(_=E.action);return}if(!d.href||typeof d.href!="string"){for(var E=d.parentNode;E&&E.nodeName&&!E.href;)E=E.parentNode;E&&(E.hash&&E.hostname===location.hostname?_=E.hash:E.href&&(_=E.href))}else d.hash?_=d.hash:_=d.href}}(),typeof d.getAttribute=="function"){d.textContent&&(B.text=d.textContent);var H=d.getAttribute("value");H&&(B.value=H);var j=d.getAttribute("title");j&&(B.title=j);var Q=d.getAttribute("id");Q&&(B.id=Q)}B.type=n.type,n.type==="keypress"?B.key=String.fromCharCode(parseInt(n.which,10)):n.type==="mousedown"&&(B.button=n.which===1?"left":n.which===2?"middle":"right");var O=f.dnt?f.dnt.split(","):[];if(_&&O.length&&z(_,O))return!1;if(O.length&&d.className&&typeof d.className=="string"){var V=d.className.split(" ");if(V.some(function(ge){return O.indexOf(ge)!==-1}))return!1}if(O.length&&d.attributes&&d.attributes.rel&&d.attributes.rel.value&&z(d.attributes.rel.value,O))return!1;var k="0,0";!T(n.pageX)&&!T(n.pageY)?k=n.pageX+","+n.pageY:T(n.clientX)||(k=n.clientX+(document.body.scrollLeft||0)+(document.documentElement.scrollLeft||0)+","+(n.clientY+(document.body.scrollTop||0)+(document.documentElement.scrollTop||0)));var I=_?"&fh="+g:"",Y="&res="+L(_)+"&pos="+k+"&no="+L(JSON.stringify(B))+I,Se="action=slimtrack&id="+f.id+Y,Ce=Date.now();if(Se===K&&Ce-re<1e3)return!1;K=Se,re=Ce;var xe=de(Se,u);if(xe)try{window.__slimstatHasInteraction=!0}catch{}return xe}function Me(n,s){var u=n&&(n.target||n.srcElement)||{},f="";try{u.href&&(f=u.href)}catch{}var w={type:n?n.type:"unknown"};s&&(w.note=s);var d="0,0";return n&&!T(n.pageX)&&!T(n.pageY)&&(d=n.pageX+","+n.pageY),"&res="+L(f)+"&pos="+d+"&no="+L(JSON.stringify(w))}var Le={excludes:{adBlock:!0,addBehavior:!0,userAgent:!0,canvas:!0,webgl:!0,colorDepth:!0,deviceMemory:!0,hardwareConcurrency:!0,sessionStorage:!0,localStorage:!0,indexedDb:!0,openDatabase:!0,cpuClass:!0,plugins:!0,webglVendorAndRenderer:!0,hasLiedLanguages:!0,hasLiedResolution:!0,hasLiedOs:!0,hasLiedBrowser:!0,fonts:!0,audio:!0}};function Ne(n){if(!T(n.id)&&parseInt(n.id,10)>0)return"action=slimtrack&id="+n.id;var s="action=slimtrack&ref="+L(document.referrer)+"&res="+L(window.location.href);return T(n.ci)||(s+="&ci="+n.ci),s}function Re(n){n=n||{},ce();var s=D(),u=n.isNavigation||!1;if(!(!u&&!T(s.id)&&parseInt(s.id,10)>0)){u&&(s.id=null);var f=Ne(s);if(f&&!U){var w=Date.now();if(!(f===C&&w-x<150)){C=f,x=w;var d=b.empty(s.id)||parseInt(s.id,10)<=0,B=!d;if(!(v&&d)){v=d,U=!0;var _=function(){Ue.default.get(Le,function(H){ie(H),de(f+Te(H),B,{immediate:T(s.id)}),Ae(),v=!1,U=!1,setTimeout(function(){U=!1},100)})};window.requestIdleCallback?window.requestIdleCallback(_):setTimeout(_,250)}}}}}function Ae(){for(var n=D(),s=n.oc?n.oc.split(","):[],u=s.length>0,f=0;f<s.length;f++)if(J(s[f])){u=!1;break}if(!u)return!1;var w;try{w=new XMLHttpRequest}catch{return!1}return w.open("POST",n.ajaxurl,!0),w.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),w.setRequestHeader("X-Requested-With","XMLHttpRequest"),w.withCredentials=!0,w.onreadystatechange=function(){if(w.readyState===4&&w.status===200){var d=document.createElement("div");d.innerHTML=w.responseText,document.body.appendChild(d)}},w.send("action=slimstat_optout_html"),!0}function De(n,s){n=n||window.event,n&&n.preventDefault?n.preventDefault():n&&(n.returnValue=!1);var u=D(),f=new Date(Date.now()+31536e6);document.cookie="slimstat_optout_tracking="+s+";path="+(u.baseurl||"/")+";expires="+f.toGMTString();var w=n.target||n.srcElement;w&&w.parentNode&&w.parentNode.parentNode&&w.parentNode.parentNode.removeChild(w.parentNode)}return{base64_key_str:p,get fingerprint_hash(){return g},set fingerprint_hash(n){g=n},utf8_encode:S,base64_encode:L,get_page_performance:fe,get_server_latency:q,optout:De,show_optout_message:Ae,add_event:ae,in_array:z,empty:T,get_cookie:J,send_to_server:de,ss_track:Ie,init_fingerprint_hash:ie,get_slimstat_data:Te,get_component_value:ne,_extract_params:ce,_send_pageview:Re,_assign_runtime_helpers:function(n){W=n.pendingInteractions,ue=n.loadOfflineQueue,le=n.saveOfflineQueue,D=n.currentSlimStatParams,U=n.pageviewInProgress}}}();Element.prototype.matches||(Element.prototype.matches=Element.prototype.matchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector||Element.prototype.oMatchesSelector||Element.prototype.webkitMatchesSelector||function(p){for(var g=(this.document||this.ownerDocument).querySelectorAll(p),C=g.length;--C>=0&&g.item(C)!==this;);return C>-1});String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")});Array.isArray||(Array.isArray=function(p){return Object.prototype.toString.call(p)==="[object Array]"});window.requestIdleCallback||(window.requestIdleCallback=function(p){return setTimeout(p,250)});(function(){var g=[],C="slimstat_offline_queue",x=!1;function v(){try{var m=localStorage.getItem(C);if(!m)return[];var S=JSON.parse(m);return Array.isArray(S)?S:[]}catch{return[]}}function P(m){try{localStorage.setItem(C,JSON.stringify(m.slice(-200)))}catch{}}function F(){return window.SlimStatParams||(window.SlimStatParams={}),window.SlimStatParams}b._assign_runtime_helpers({pendingInteractions:g,loadOfflineQueue:v,saveOfflineQueue:P,currentSlimStatParams:F,pageviewInProgress:x});var te={},G=!1,K="",re=0,pe=1e3;try{typeof window.__slimstatHasInteraction>"u"&&(window.__slimstatHasInteraction=!1)}catch{}function W(m){var S=window.SlimStatParams||{};if(!(!S.id||parseInt(S.id,10)<=0||te[S.id])){var L=Date.now();if(!(G||m===K&&L-re<pe)){G=!0,K=m,re=L;var T="action=slimtrack&id="+S.id+(m?"&fv="+encodeURIComponent(m):"");b.send_to_server(T,!0,{priority:"high",immediate:!1}),te[S.id]=!0,setTimeout(function(){G=!1},120)}}}var ue=JSON.stringify(window.SlimStatParams||{}),le=new MutationObserver(function(){var m=window.SlimStatParams||{};if(b.empty(m.id)||parseInt(m.id,10)<=0){b._extract_params();var S=JSON.stringify(window.SlimStatParams||{});S!==ue&&(ue=S)}});le.observe(document.head,{childList:!0,subtree:!0}),le.observe(document.body,{childList:!0,subtree:!0}),b.add_event(window,"load",function(){b._extract_params(),b._send_pageview(),setTimeout(function(){try{navigator.onLine!==!1&&typeof flushOfflineQueue=="function"&&flushOfflineQueue()}catch{}},500)}),b.add_event(document,"visibilitychange",function(){var m=window.SlimStatParams||{};document.visibilityState==="hidden"&&m.id&&parseInt(m.id,10)>0&&U("visibility")}),b.add_event(window,"pagehide",function(){var m=window.SlimStatParams||{};m.id&&parseInt(m.id,10)>0&&U("pagehide")}),b.add_event(window,"beforeunload",function(){var m=window.SlimStatParams||{};m.id&&parseInt(m.id,10)>0&&U("beforeunload")});var D=null;function U(m){var S=window.SlimStatParams||{};!S.id||te[S.id]||(D&&clearTimeout(D),D=setTimeout(function(){W(m)},50))}b.add_event(window,"online",function(){flushOfflineQueue(),flushPendingInteractions()}),b.add_event(window,"beforeunload",function(){var m=F();if((!m.id||parseInt(m.id,10)<=0)&&g.length>0){var S=v();g.forEach(function(L){var T="action=slimtrack&id=pending"+L;S.push({p:T,t:Date.now()})}),P(S),g.length=0}});function me(){b.add_event(document.body,"click",function(m){for(var S=m.target;S&&S!==document.body;){if(S.matches&&S.matches("a,button,input,area")){b.ss_track(m,null,null);break}S=S.parentNode}})}function we(){if(b.add_event(document,"wp-interactivity:navigate",function(){if(!x){var T=window.location.pathname,z=window.location.search;setTimeout(function(){var J=window.location.pathname,ae=window.location.search;if(J!==T||ae!==z){var q=F();q.id&&parseInt(q.id,10)>0&&U("navigation"),b._send_pageview({isNavigation:!0})}},150)}}),window.history&&history.pushState){var m=history.pushState,S=history.replaceState,L=function(T){var z=window.location.pathname,J=window.location.search,ae=T?S:m,q=Array.prototype.slice.call(arguments,1),fe=ae.apply(this,q);return setTimeout(function(){var ne=window.location.pathname,ce=window.location.search;if(ne!==z||ce!==J){var ie=F();ie.id&&parseInt(ie.id,10)>0&&U("history"),b._send_pageview({isNavigation:!0})}},150),fe};history.pushState=function(){var T=Array.prototype.slice.call(arguments);return T.unshift(!1),L.apply(this,T)},history.replaceState=function(){var T=Array.prototype.slice.call(arguments);return T.unshift(!0),L.apply(this,T)},b.add_event(window,"popstate",function(){x||setTimeout(function(){F().id=null,b._send_pageview({isNavigation:!0})},150)})}}me(),we()})();})(); +(()=>{var Me=function(){return Me=Object.assign||function(t){for(var n,i=1,c=arguments.length;i<c;i++){n=arguments[i];for(var a in n)Object.prototype.hasOwnProperty.call(n,a)&&(t[a]=n[a])}return t},Me.apply(this,arguments)};function le(e,t,n,i){function c(a){return a instanceof n?a:new n(function(u){u(a)})}return new(n||(n=Promise))(function(a,u){function v(p){try{f(i.next(p))}catch(l){u(l)}}function d(p){try{f(i.throw(p))}catch(l){u(l)}}function f(p){p.done?a(p.value):c(p.value).then(v,d)}f((i=i.apply(e,t||[])).next())})}function de(e,t){var n={label:0,sent:function(){if(a[0]&1)throw a[1];return a[1]},trys:[],ops:[]},i,c,a,u=Object.create((typeof Iterator=="function"?Iterator:Object).prototype);return u.next=v(0),u.throw=v(1),u.return=v(2),typeof Symbol=="function"&&(u[Symbol.iterator]=function(){return this}),u;function v(f){return function(p){return d([f,p])}}function d(f){if(i)throw new TypeError("Generator is already executing.");for(;u&&(u=0,f[0]&&(n=0)),n;)try{if(i=1,c&&(a=f[0]&2?c.return:f[0]?c.throw||((a=c.return)&&a.call(c),0):c.next)&&!(a=a.call(c,f[1])).done)return a;switch(c=0,a&&(f=[f[0]&2,a.value]),f[0]){case 0:case 1:a=f;break;case 4:return n.label++,{value:f[1],done:!1};case 5:n.label++,c=f[1],f=[0];continue;case 7:f=n.ops.pop(),n.trys.pop();continue;default:if(a=n.trys,!(a=a.length>0&&a[a.length-1])&&(f[0]===6||f[0]===2)){n=0;continue}if(f[0]===3&&(!a||f[1]>a[0]&&f[1]<a[3])){n.label=f[1];break}if(f[0]===6&&n.label<a[1]){n.label=a[1],a=f;break}if(a&&n.label<a[2]){n.label=a[2],n.ops.push(f);break}a[2]&&n.ops.pop(),n.trys.pop();continue}f=t.call(e,n)}catch(p){f=[6,p],c=0}finally{i=a=0}if(f[0]&5)throw f[1];return{value:f[0]?f[1]:void 0,done:!0}}}function Ze(e,t,n){if(n||arguments.length===2)for(var i=0,c=t.length,a;i<c;i++)(a||!(i in t))&&(a||(a=Array.prototype.slice.call(t,0,i)),a[i]=t[i]);return e.concat(a||Array.prototype.slice.call(t))}var yt="4.6.2";function De(e,t){return new Promise(function(n){return setTimeout(n,e,t)})}function Xt(){return new Promise(function(e){var t=new MessageChannel;t.port1.onmessage=function(){return e()},t.port2.postMessage(null)})}function Yt(e,t){t===void 0&&(t=1/0);var n=window.requestIdleCallback;return n?new Promise(function(i){return n.call(window,function(){return i()},{timeout:t})}):De(Math.min(e,t))}function bt(e){return!!e&&typeof e.then=="function"}function ot(e,t){try{var n=e();bt(n)?n.then(function(i){return t(!0,i)},function(i){return t(!1,i)}):t(!0,n)}catch(i){t(!1,i)}}function st(e,t,n){return n===void 0&&(n=16),le(this,void 0,void 0,function(){var i,c,a,u;return de(this,function(v){switch(v.label){case 0:i=Array(e.length),c=Date.now(),a=0,v.label=1;case 1:return a<e.length?(i[a]=t(e[a],a),u=Date.now(),u>=c+n?(c=u,[4,Xt()]):[3,3]):[3,4];case 2:v.sent(),v.label=3;case 3:return++a,[3,1];case 4:return[2,i]}})})}function Te(e){return e.then(void 0,function(){}),e}function Bt(e,t){for(var n=0,i=e.length;n<i;++n)if(e[n]===t)return!0;return!1}function zt(e,t){return!Bt(e,t)}function Je(e){return parseInt(e)}function ve(e){return parseFloat(e)}function we(e,t){return typeof e=="number"&&isNaN(e)?t:e}function re(e){return e.reduce(function(t,n){return t+(n?1:0)},0)}function _t(e,t){if(t===void 0&&(t=1),Math.abs(t)>=1)return Math.round(e/t)*t;var n=1/t;return Math.round(e*n)/n}function Jt(e){for(var t,n,i="Unexpected syntax '".concat(e,"'"),c=/^\s*([a-z-]*)(.*)$/i.exec(e),a=c[1]||void 0,u={},v=/([.:#][\w-]+|\[.+?\])/gi,d=function(_,k){u[_]=u[_]||[],u[_].push(k)};;){var f=v.exec(c[2]);if(!f)break;var p=f[0];switch(p[0]){case".":d("class",p.slice(1));break;case"#":d("id",p.slice(1));break;case"[":{var l=/^\[([\w-]+)([~|^$*]?=("(.*?)"|([\w-]+)))?(\s+[is])?\]$/.exec(p);if(l)d(l[1],(n=(t=l[4])!==null&&t!==void 0?t:l[5])!==null&&n!==void 0?n:"");else throw new Error(i);break}default:throw new Error(i)}}return[a,u]}function Ut(e){for(var t=new Uint8Array(e.length),n=0;n<e.length;n++){var i=e.charCodeAt(n);if(i>127)return new TextEncoder().encode(e);t[n]=i}return t}function _e(e,t){var n=e[0]>>>16,i=e[0]&65535,c=e[1]>>>16,a=e[1]&65535,u=t[0]>>>16,v=t[0]&65535,d=t[1]>>>16,f=t[1]&65535,p=0,l=0,_=0,k=0;k+=a+f,_+=k>>>16,k&=65535,_+=c+d,l+=_>>>16,_&=65535,l+=i+v,p+=l>>>16,l&=65535,p+=n+u,p&=65535,e[0]=p<<16|l,e[1]=_<<16|k}function ce(e,t){var n=e[0]>>>16,i=e[0]&65535,c=e[1]>>>16,a=e[1]&65535,u=t[0]>>>16,v=t[0]&65535,d=t[1]>>>16,f=t[1]&65535,p=0,l=0,_=0,k=0;k+=a*f,_+=k>>>16,k&=65535,_+=c*f,l+=_>>>16,_&=65535,_+=a*d,l+=_>>>16,_&=65535,l+=i*f,p+=l>>>16,l&=65535,l+=c*d,p+=l>>>16,l&=65535,l+=a*v,p+=l>>>16,l&=65535,p+=n*f+i*d+c*v+a*u,p&=65535,e[0]=p<<16|l,e[1]=_<<16|k}function Pe(e,t){var n=e[0];t%=64,t===32?(e[0]=e[1],e[1]=n):t<32?(e[0]=n<<t|e[1]>>>32-t,e[1]=e[1]<<t|n>>>32-t):(t-=32,e[0]=e[1]<<t|n>>>32-t,e[1]=n<<t|e[1]>>>32-t)}function oe(e,t){t%=64,t!==0&&(t<32?(e[0]=e[1]>>>32-t,e[1]=e[1]<<t):(e[0]=e[1]<<t-32,e[1]=0))}function U(e,t){e[0]^=t[0],e[1]^=t[1]}var qt=[4283543511,3981806797],Qt=[3301882366,444984403];function ct(e){var t=[0,e[0]>>>1];U(e,t),ce(e,qt),t[1]=e[0]>>>1,U(e,t),ce(e,Qt),t[1]=e[0]>>>1,U(e,t)}var je=[2277735313,289559509],Ve=[1291169091,658871167],ut=[0,5],Kt=[0,1390208809],$t=[0,944331445];function en(e,t){var n=Ut(e);t=t||0;var i=[0,n.length],c=i[1]%16,a=i[1]-c,u=[0,t],v=[0,t],d=[0,0],f=[0,0],p;for(p=0;p<a;p=p+16)d[0]=n[p+4]|n[p+5]<<8|n[p+6]<<16|n[p+7]<<24,d[1]=n[p]|n[p+1]<<8|n[p+2]<<16|n[p+3]<<24,f[0]=n[p+12]|n[p+13]<<8|n[p+14]<<16|n[p+15]<<24,f[1]=n[p+8]|n[p+9]<<8|n[p+10]<<16|n[p+11]<<24,ce(d,je),Pe(d,31),ce(d,Ve),U(u,d),Pe(u,27),_e(u,v),ce(u,ut),_e(u,Kt),ce(f,Ve),Pe(f,33),ce(f,je),U(v,f),Pe(v,31),_e(v,u),ce(v,ut),_e(v,$t);d[0]=0,d[1]=0,f[0]=0,f[1]=0;var l=[0,0];switch(c){case 15:l[1]=n[p+14],oe(l,48),U(f,l);case 14:l[1]=n[p+13],oe(l,40),U(f,l);case 13:l[1]=n[p+12],oe(l,32),U(f,l);case 12:l[1]=n[p+11],oe(l,24),U(f,l);case 11:l[1]=n[p+10],oe(l,16),U(f,l);case 10:l[1]=n[p+9],oe(l,8),U(f,l);case 9:l[1]=n[p+8],U(f,l),ce(f,Ve),Pe(f,33),ce(f,je),U(v,f);case 8:l[1]=n[p+7],oe(l,56),U(d,l);case 7:l[1]=n[p+6],oe(l,48),U(d,l);case 6:l[1]=n[p+5],oe(l,40),U(d,l);case 5:l[1]=n[p+4],oe(l,32),U(d,l);case 4:l[1]=n[p+3],oe(l,24),U(d,l);case 3:l[1]=n[p+2],oe(l,16),U(d,l);case 2:l[1]=n[p+1],oe(l,8),U(d,l);case 1:l[1]=n[p],U(d,l),ce(d,je),Pe(d,31),ce(d,Ve),U(u,d)}return U(u,i),U(v,i),_e(u,v),_e(v,u),ct(u),ct(v),_e(u,v),_e(v,u),("00000000"+(u[0]>>>0).toString(16)).slice(-8)+("00000000"+(u[1]>>>0).toString(16)).slice(-8)+("00000000"+(v[0]>>>0).toString(16)).slice(-8)+("00000000"+(v[1]>>>0).toString(16)).slice(-8)}function tn(e){var t;return Me({name:e.name,message:e.message,stack:(t=e.stack)===null||t===void 0?void 0:t.split(` +`)},e)}function nn(e){return/^function\s.*?\{\s*\[native code]\s*}$/.test(String(e))}function rn(e){return typeof e!="function"}function an(e,t){var n=Te(new Promise(function(i){var c=Date.now();ot(e.bind(null,t),function(){for(var a=[],u=0;u<arguments.length;u++)a[u]=arguments[u];var v=Date.now()-c;if(!a[0])return i(function(){return{error:a[1],duration:v}});var d=a[1];if(rn(d))return i(function(){return{value:d,duration:v}});i(function(){return new Promise(function(f){var p=Date.now();ot(d,function(){for(var l=[],_=0;_<arguments.length;_++)l[_]=arguments[_];var k=v+Date.now()-p;if(!l[0])return f({error:l[1],duration:k});f({value:l[1],duration:k})})})})})}));return function(){return n.then(function(c){return c()})}}function on(e,t,n,i){var c=Object.keys(e).filter(function(u){return zt(n,u)}),a=Te(st(c,function(u){return an(e[u],t)},i));return function(){return le(this,void 0,void 0,function(){var v,d,f,p,l;return de(this,function(_){switch(_.label){case 0:return[4,a];case 1:return v=_.sent(),[4,st(v,function(k){return Te(k())},i)];case 2:return d=_.sent(),[4,Promise.all(d)];case 3:for(f=_.sent(),p={},l=0;l<c.length;++l)p[c[l]]=f[l];return[2,p]}})})}}function St(){var e=window,t=navigator;return re(["MSCSSMatrix"in e,"msSetImmediate"in e,"msIndexedDB"in e,"msMaxTouchPoints"in t,"msPointerEnabled"in t])>=4}function sn(){var e=window,t=navigator;return re(["msWriteProfilerMark"in e,"MSStream"in e,"msLaunchUri"in t,"msSaveBlob"in t])>=3&&!St()}function Ae(){var e=window,t=navigator;return re(["webkitPersistentStorage"in t,"webkitTemporaryStorage"in t,(t.vendor||"").indexOf("Google")===0,"webkitResolveLocalFileSystemURL"in e,"BatteryManager"in e,"webkitMediaStream"in e,"webkitSpeechGrammar"in e])>=5}function pe(){var e=window,t=navigator;return re(["ApplePayError"in e,"CSSPrimitiveValue"in e,"Counter"in e,t.vendor.indexOf("Apple")===0,"RGBColor"in e,"WebKitMediaKeys"in e])>=4}function Ue(){var e=window,t=e.HTMLElement,n=e.Document;return re(["safari"in e,!("ongestureend"in e),!("TouchEvent"in e),!("orientation"in e),t&&!("autocapitalize"in t.prototype),n&&"pointerLockElement"in n.prototype])>=4}function Ne(){var e=window;return nn(e.print)&&String(e.browser)==="[object WebPageNamespace]"}function Ct(){var e,t,n=window;return re(["buildID"in navigator,"MozAppearance"in((t=(e=document.documentElement)===null||e===void 0?void 0:e.style)!==null&&t!==void 0?t:{}),"onmozfullscreenchange"in n,"mozInnerScreenX"in n,"CSSMozDocumentRule"in n,"CanvasCaptureMediaStream"in n])>=4}function cn(){var e=window;return re([!("MediaSettingsRange"in e),"RTCEncodedAudioFrame"in e,""+e.Intl=="[object Intl]",""+e.Reflect=="[object Reflect]"])>=3}function un(){var e=window,t=e.URLPattern;return re(["union"in Set.prototype,"Iterator"in e,t&&"hasRegExpGroups"in t.prototype,"RGB8"in WebGLRenderingContext.prototype])>=3}function fn(){var e=window;return re(["DOMRectList"in e,"RTCPeerConnectionIceEvent"in e,"SVGGeometryElement"in e,"ontransitioncancel"in e])>=3}function Fe(){var e=window,t=navigator,n=e.CSS,i=e.HTMLButtonElement;return re([!("getStorageUpdates"in t),i&&"popover"in i.prototype,"CSSCounterStyleRule"in e,n.supports("font-size-adjust: ex-height 0.5"),n.supports("text-transform: full-width")])>=4}function ln(){if(navigator.platform==="iPad")return!0;var e=screen,t=e.width/e.height;return re(["MediaSource"in window,!!Element.prototype.webkitRequestFullscreen,t>.65&&t<1.53])>=2}function dn(){var e=document;return e.fullscreenElement||e.msFullscreenElement||e.mozFullScreenElement||e.webkitFullscreenElement||null}function vn(){var e=document;return(e.exitFullscreen||e.msExitFullscreen||e.mozCancelFullScreen||e.webkitExitFullscreen).call(e)}function qe(){var e=Ae(),t=Ct(),n=window,i=navigator,c="connection";return e?re([!("SharedWorker"in n),i[c]&&"ontypechange"in i[c],!("sinkId"in new Audio)])>=2:t?re(["onorientationchange"in n,"orientation"in n,/android/i.test(i.appVersion)])>=2:!1}function pn(){var e=navigator,t=window,n=Audio.prototype,i=t.visualViewport;return re(["srLatency"in n,"srChannelCount"in n,"devicePosture"in e,i&&"segments"in i,"getTextInformation"in Image.prototype])>=3}function mn(){return wn()?-4:hn()}function hn(){var e=window,t=e.OfflineAudioContext||e.webkitOfflineAudioContext;if(!t)return-2;if(gn())return-1;var n=4500,i=5e3,c=new t(1,i,44100),a=c.createOscillator();a.type="triangle",a.frequency.value=1e4;var u=c.createDynamicsCompressor();u.threshold.value=-50,u.knee.value=40,u.ratio.value=12,u.attack.value=0,u.release.value=.25,a.connect(u),u.connect(c.destination),a.start(0);var v=yn(c),d=v[0],f=v[1],p=Te(d.then(function(l){return bn(l.getChannelData(0).subarray(n))},function(l){if(l.name==="timeout"||l.name==="suspended")return-3;throw l}));return function(){return f(),p}}function gn(){return pe()&&!Ue()&&!fn()}function wn(){return pe()&&Fe()&&Ne()||Ae()&&pn()&&un()}function yn(e){var t=3,n=500,i=500,c=5e3,a=function(){},u=new Promise(function(v,d){var f=!1,p=0,l=0;e.oncomplete=function(G){return v(G.renderedBuffer)};var _=function(){setTimeout(function(){return d(ft("timeout"))},Math.min(i,l+c-Date.now()))},k=function(){try{var G=e.startRendering();switch(bt(G)&&Te(G),e.state){case"running":l=Date.now(),f&&_();break;case"suspended":document.hidden||p++,f&&p>=t?d(ft("suspended")):setTimeout(k,n);break}}catch(N){d(N)}};k(),a=function(){f||(f=!0,l>0&&_())}});return[u,a]}function bn(e){for(var t=0,n=0;n<e.length;++n)t+=Math.abs(e[n]);return t}function ft(e){var t=new Error(e);return t.name=e,t}function kt(e,t,n){var i,c,a;return n===void 0&&(n=50),le(this,void 0,void 0,function(){var u,v;return de(this,function(d){switch(d.label){case 0:u=document,d.label=1;case 1:return u.body?[3,3]:[4,De(n)];case 2:return d.sent(),[3,1];case 3:v=u.createElement("iframe"),d.label=4;case 4:return d.trys.push([4,,10,11]),[4,new Promise(function(f,p){var l=!1,_=function(){l=!0,f()},k=function(z){l=!0,p(z)};v.onload=_,v.onerror=k;var G=v.style;G.setProperty("display","block","important"),G.position="absolute",G.top="0",G.left="0",G.visibility="hidden",t&&"srcdoc"in v?v.srcdoc=t:v.src="about:blank",u.body.appendChild(v);var N=function(){var z,F;l||(((F=(z=v.contentWindow)===null||z===void 0?void 0:z.document)===null||F===void 0?void 0:F.readyState)==="complete"?_():setTimeout(N,10))};N()})];case 5:d.sent(),d.label=6;case 6:return!((c=(i=v.contentWindow)===null||i===void 0?void 0:i.document)===null||c===void 0)&&c.body?[3,8]:[4,De(n)];case 7:return d.sent(),[3,6];case 8:return[4,e(v,v.contentWindow)];case 9:return[2,d.sent()];case 10:return(a=v.parentNode)===null||a===void 0||a.removeChild(v),[7];case 11:return[2]}})})}function _n(e){for(var t=Jt(e),n=t[0],i=t[1],c=document.createElement(n??"div"),a=0,u=Object.keys(i);a<u.length;a++){var v=u[a],d=i[v].join(" ");v==="style"?Sn(c.style,d):c.setAttribute(v,d)}return c}function Sn(e,t){for(var n=0,i=t.split(";");n<i.length;n++){var c=i[n],a=/^\s*([\w-]+)\s*:\s*(.+?)(\s*!([\w-]+))?\s*$/.exec(c);if(a){var u=a[1],v=a[2],d=a[4];e.setProperty(u,v,d||"")}}}function Cn(){for(var e=window;;){var t=e.parent;if(!t||t===e)return!1;try{if(t.location.origin!==e.location.origin)return!0}catch(n){if(n instanceof Error&&n.name==="SecurityError")return!0;throw n}e=t}}var kn="mmMwWLliI0O&1",Ln="48px",xe=["monospace","sans-serif","serif"],lt=["sans-serif-thin","ARNO PRO","Agency FB","Arabic Typesetting","Arial Unicode MS","AvantGarde Bk BT","BankGothic Md BT","Batang","Bitstream Vera Sans Mono","Calibri","Century","Century Gothic","Clarendon","EUROSTILE","Franklin Gothic","Futura Bk BT","Futura Md BT","GOTHAM","Gill Sans","HELV","Haettenschweiler","Helvetica Neue","Humanst521 BT","Leelawadee","Letter Gothic","Levenim MT","Lucida Bright","Lucida Sans","Menlo","MS Mincho","MS Outlook","MS Reference Specialty","MS UI Gothic","MT Extra","MYRIAD PRO","Marlett","Meiryo UI","Microsoft Uighur","Minion Pro","Monotype Corsiva","PMingLiU","Pristina","SCRIPTINA","Segoe UI Light","Serifa","SimHei","Small Fonts","Staccato222 BT","TRAJAN PRO","Univers CE 55 Medium","Vrinda","ZWAdobeF"];function En(){var e=this;return kt(function(t,n){var i=n.document;return le(e,void 0,void 0,function(){var c,a,u,v,d,f,p,l,_,k,G,N;return de(this,function(z){for(c=i.body,c.style.fontSize=Ln,a=i.createElement("div"),a.style.setProperty("visibility","hidden","important"),u={},v={},d=function(F){var Y=i.createElement("span"),K=Y.style;return K.position="absolute",K.top="0",K.left="0",K.fontFamily=F,Y.textContent=kn,a.appendChild(Y),Y},f=function(F,Y){return d("'".concat(F,"',").concat(Y))},p=function(){return xe.map(d)},l=function(){for(var F={},Y=function(T){F[T]=xe.map(function(se){return f(T,se)})},K=0,$=lt;K<$.length;K++){var ee=$[K];Y(ee)}return F},_=function(F){return xe.some(function(Y,K){return F[K].offsetWidth!==u[Y]||F[K].offsetHeight!==v[Y]})},k=p(),G=l(),c.appendChild(a),N=0;N<xe.length;N++)u[xe[N]]=k[N].offsetWidth,v[xe[N]]=k[N].offsetHeight;return[2,lt.filter(function(F){return _(G[F])})]})})})}function Pn(){var e=navigator.plugins;if(e){for(var t=[],n=0;n<e.length;++n){var i=e[n];if(i){for(var c=[],a=0;a<i.length;++a){var u=i[a];c.push({type:u.type,suffixes:u.suffixes})}t.push({name:i.name,description:i.description,mimeTypes:c})}}return t}}function xn(){return In(Mn())}function In(e){var t,n=!1,i,c,a=Rn(),u=a[0],v=a[1];return Tn(u,v)?(n=An(v),e?i=c="skipped":(t=Nn(u,v),i=t[0],c=t[1])):i=c="unsupported",{winding:n,geometry:i,text:c}}function Rn(){var e=document.createElement("canvas");return e.width=1,e.height=1,[e,e.getContext("2d")]}function Tn(e,t){return!!(t&&e.toDataURL)}function An(e){return e.rect(0,0,10,10),e.rect(2,2,6,6),!e.isPointInPath(5,5,"evenodd")}function Nn(e,t){Fn(e,t);var n=He(e),i=He(e);if(n!==i)return["unstable","unstable"];On(e,t);var c=He(e);return[c,n]}function Fn(e,t){e.width=240,e.height=60,t.textBaseline="alphabetic",t.fillStyle="#f60",t.fillRect(100,1,62,20),t.fillStyle="#069",t.font='11pt "Times New Roman"';var n="Cwm fjordbank gly ".concat("\u{1F603}");t.fillText(n,2,15),t.fillStyle="rgba(102, 204, 0, 0.2)",t.font="18pt Arial",t.fillText(n,4,45)}function On(e,t){e.width=122,e.height=110,t.globalCompositeOperation="multiply";for(var n=0,i=[["#f2f",40,40],["#2ff",80,40],["#ff2",60,80]];n<i.length;n++){var c=i[n],a=c[0],u=c[1],v=c[2];t.fillStyle=a,t.beginPath(),t.arc(u,v,40,0,Math.PI*2,!0),t.closePath(),t.fill()}t.fillStyle="#f9c",t.arc(60,60,60,0,Math.PI*2,!0),t.arc(60,60,20,0,Math.PI*2,!0),t.fill("evenodd")}function He(e){return e.toDataURL()}function Mn(){return pe()&&Fe()&&Ne()}function jn(){var e=navigator,t=0,n;e.maxTouchPoints!==void 0?t=Je(e.maxTouchPoints):e.msMaxTouchPoints!==void 0&&(t=e.msMaxTouchPoints);try{document.createEvent("TouchEvent"),n=!0}catch{n=!1}var i="ontouchstart"in window;return{maxTouchPoints:t,touchEvent:n,touchStart:i}}function Vn(){return navigator.oscpu}function Wn(){var e=navigator,t=[],n=e.language||e.userLanguage||e.browserLanguage||e.systemLanguage;if(n!==void 0&&t.push([n]),Array.isArray(e.languages))Ae()&&cn()||t.push(e.languages);else if(typeof e.languages=="string"){var i=e.languages;i&&t.push(i.split(","))}return t}function Dn(){return window.screen.colorDepth}function Gn(){return we(ve(navigator.deviceMemory),void 0)}function Zn(){if(!(pe()&&Fe()&&Ne()))return Hn()}function Hn(){var e=screen,t=function(i){return we(Je(i),null)},n=[t(e.width),t(e.height)];return n.sort().reverse(),n}var Xn=2500,Yn=10,We,Xe;function Bn(){if(Xe===void 0){var e=function(){var t=Be();ze(t)?Xe=setTimeout(e,Xn):(We=t,Xe=void 0)};e()}}function zn(){var e=this;return Bn(),function(){return le(e,void 0,void 0,function(){var t;return de(this,function(n){switch(n.label){case 0:return t=Be(),ze(t)?We?[2,Ze([],We,!0)]:dn()?[4,vn()]:[3,2]:[3,2];case 1:n.sent(),t=Be(),n.label=2;case 2:return ze(t)||(We=t),[2,t]}})})}}function Jn(){var e=this;if(pe()&&Fe()&&Ne())return function(){return Promise.resolve(void 0)};var t=zn();return function(){return le(e,void 0,void 0,function(){var n,i;return de(this,function(c){switch(c.label){case 0:return[4,t()];case 1:return n=c.sent(),i=function(a){return a===null?null:_t(a,Yn)},[2,[i(n[0]),i(n[1]),i(n[2]),i(n[3])]]}})})}}function Be(){var e=screen;return[we(ve(e.availTop),null),we(ve(e.width)-ve(e.availWidth)-we(ve(e.availLeft),0),null),we(ve(e.height)-ve(e.availHeight)-we(ve(e.availTop),0),null),we(ve(e.availLeft),null)]}function ze(e){for(var t=0;t<4;++t)if(e[t])return!1;return!0}function Un(){return we(Je(navigator.hardwareConcurrency),void 0)}function qn(){var e,t=(e=window.Intl)===null||e===void 0?void 0:e.DateTimeFormat;if(t){var n=new t().resolvedOptions().timeZone;if(n)return n}var i=-Qn();return"UTC".concat(i>=0?"+":"").concat(i)}function Qn(){var e=new Date().getFullYear();return Math.max(ve(new Date(e,0,1).getTimezoneOffset()),ve(new Date(e,6,1).getTimezoneOffset()))}function Kn(){try{return!!window.sessionStorage}catch{return!0}}function $n(){try{return!!window.localStorage}catch{return!0}}function er(){if(!(St()||sn()))try{return!!window.indexedDB}catch{return!0}}function tr(){return!!window.openDatabase}function nr(){return navigator.cpuClass}function rr(){var e=navigator.platform;return e==="MacIntel"&&pe()&&!Ue()?ln()?"iPad":"iPhone":e}function ir(){return navigator.vendor||""}function ar(){for(var e=[],t=0,n=["chrome","safari","__crWeb","__gCrWeb","yandex","__yb","__ybro","__firefox__","__edgeTrackingPreventionStatistics","webkit","oprt","samsungAr","ucweb","UCShellJava","puffinDevice"];t<n.length;t++){var i=n[t],c=window[i];c&&typeof c=="object"&&e.push(i)}return e.sort()}function or(){var e=document;try{e.cookie="cookietest=1; SameSite=Strict;";var t=e.cookie.indexOf("cookietest=")!==-1;return e.cookie="cookietest=1; SameSite=Strict; expires=Thu, 01-Jan-1970 00:00:01 GMT",t}catch{return!1}}function sr(){var e=atob;return{abpIndo:["#Iklan-Melayang","#Kolom-Iklan-728","#SidebarIklan-wrapper",'[title="ALIENBOLA" i]',e("I0JveC1CYW5uZXItYWRz")],abpvn:[".quangcao","#mobileCatfish",e("LmNsb3NlLWFkcw=="),'[id^="bn_bottom_fixed_"]',"#pmadv"],adBlockFinland:[".mainostila",e("LnNwb25zb3JpdA=="),".ylamainos",e("YVtocmVmKj0iL2NsaWNrdGhyZ2guYXNwPyJd"),e("YVtocmVmXj0iaHR0cHM6Ly9hcHAucmVhZHBlYWsuY29tL2FkcyJd")],adBlockPersian:["#navbar_notice_50",".kadr",'TABLE[width="140px"]',"#divAgahi",e("YVtocmVmXj0iaHR0cDovL2cxLnYuZndtcm0ubmV0L2FkLyJd")],adBlockWarningRemoval:["#adblock-honeypot",".adblocker-root",".wp_adblock_detect",e("LmhlYWRlci1ibG9ja2VkLWFk"),e("I2FkX2Jsb2NrZXI=")],adGuardAnnoyances:[".hs-sosyal","#cookieconsentdiv",'div[class^="app_gdpr"]',".as-oil",'[data-cypress="soft-push-notification-modal"]'],adGuardBase:[".BetterJsPopOverlay",e("I2FkXzMwMFgyNTA="),e("I2Jhbm5lcmZsb2F0MjI="),e("I2NhbXBhaWduLWJhbm5lcg=="),e("I0FkLUNvbnRlbnQ=")],adGuardChinese:[e("LlppX2FkX2FfSA=="),e("YVtocmVmKj0iLmh0aGJldDM0LmNvbSJd"),"#widget-quan",e("YVtocmVmKj0iLzg0OTkyMDIwLnh5eiJd"),e("YVtocmVmKj0iLjE5NTZobC5jb20vIl0=")],adGuardFrench:["#pavePub",e("LmFkLWRlc2t0b3AtcmVjdGFuZ2xl"),".mobile_adhesion",".widgetadv",e("LmFkc19iYW4=")],adGuardGerman:['aside[data-portal-id="leaderboard"]'],adGuardJapanese:["#kauli_yad_1",e("YVtocmVmXj0iaHR0cDovL2FkMi50cmFmZmljZ2F0ZS5uZXQvIl0="),e("Ll9wb3BJbl9pbmZpbml0ZV9hZA=="),e("LmFkZ29vZ2xl"),e("Ll9faXNib29zdFJldHVybkFk")],adGuardMobile:[e("YW1wLWF1dG8tYWRz"),e("LmFtcF9hZA=="),'amp-embed[type="24smi"]',"#mgid_iframe1",e("I2FkX2ludmlld19hcmVh")],adGuardRussian:[e("YVtocmVmXj0iaHR0cHM6Ly9hZC5sZXRtZWFkcy5jb20vIl0="),e("LnJlY2xhbWE="),'div[id^="smi2adblock"]',e("ZGl2W2lkXj0iQWRGb3hfYmFubmVyXyJd"),"#psyduckpockeball"],adGuardSocial:[e("YVtocmVmXj0iLy93d3cuc3R1bWJsZXVwb24uY29tL3N1Ym1pdD91cmw9Il0="),e("YVtocmVmXj0iLy90ZWxlZ3JhbS5tZS9zaGFyZS91cmw/Il0="),".etsy-tweet","#inlineShare",".popup-social"],adGuardSpanishPortuguese:["#barraPublicidade","#Publicidade","#publiEspecial","#queTooltip",".cnt-publi"],adGuardTrackingProtection:["#qoo-counter",e("YVtocmVmXj0iaHR0cDovL2NsaWNrLmhvdGxvZy5ydS8iXQ=="),e("YVtocmVmXj0iaHR0cDovL2hpdGNvdW50ZXIucnUvdG9wL3N0YXQucGhwIl0="),e("YVtocmVmXj0iaHR0cDovL3RvcC5tYWlsLnJ1L2p1bXAiXQ=="),"#top100counter"],adGuardTurkish:["#backkapat",e("I3Jla2xhbWk="),e("YVtocmVmXj0iaHR0cDovL2Fkc2Vydi5vbnRlay5jb20udHIvIl0="),e("YVtocmVmXj0iaHR0cDovL2l6bGVuemkuY29tL2NhbXBhaWduLyJd"),e("YVtocmVmXj0iaHR0cDovL3d3dy5pbnN0YWxsYWRzLm5ldC8iXQ==")],bulgarian:[e("dGQjZnJlZW5ldF90YWJsZV9hZHM="),"#ea_intext_div",".lapni-pop-over","#xenium_hot_offers"],easyList:[".yb-floorad",e("LndpZGdldF9wb19hZHNfd2lkZ2V0"),e("LnRyYWZmaWNqdW5reS1hZA=="),".textad_headline",e("LnNwb25zb3JlZC10ZXh0LWxpbmtz")],easyListChina:[e("LmFwcGd1aWRlLXdyYXBbb25jbGljayo9ImJjZWJvcy5jb20iXQ=="),e("LmZyb250cGFnZUFkdk0="),"#taotaole","#aafoot.top_box",".cfa_popup"],easyListCookie:[".ezmob-footer",".cc-CookieWarning","[data-cookie-number]",e("LmF3LWNvb2tpZS1iYW5uZXI="),".sygnal24-gdpr-modal-wrap"],easyListCzechSlovak:["#onlajny-stickers",e("I3Jla2xhbW5pLWJveA=="),e("LnJla2xhbWEtbWVnYWJvYXJk"),".sklik",e("W2lkXj0ic2tsaWtSZWtsYW1hIl0=")],easyListDutch:[e("I2FkdmVydGVudGll"),e("I3ZpcEFkbWFya3RCYW5uZXJCbG9jaw=="),".adstekst",e("YVtocmVmXj0iaHR0cHM6Ly94bHR1YmUubmwvY2xpY2svIl0="),"#semilo-lrectangle"],easyListGermany:["#SSpotIMPopSlider",e("LnNwb25zb3JsaW5rZ3J1ZW4="),e("I3dlcmJ1bmdza3k="),e("I3Jla2xhbWUtcmVjaHRzLW1pdHRl"),e("YVtocmVmXj0iaHR0cHM6Ly9iZDc0Mi5jb20vIl0=")],easyListItaly:[e("LmJveF9hZHZfYW5udW5jaQ=="),".sb-box-pubbliredazionale",e("YVtocmVmXj0iaHR0cDovL2FmZmlsaWF6aW9uaWFkcy5zbmFpLml0LyJd"),e("YVtocmVmXj0iaHR0cHM6Ly9hZHNlcnZlci5odG1sLml0LyJd"),e("YVtocmVmXj0iaHR0cHM6Ly9hZmZpbGlhemlvbmlhZHMuc25haS5pdC8iXQ==")],easyListLithuania:[e("LnJla2xhbW9zX3RhcnBhcw=="),e("LnJla2xhbW9zX251b3JvZG9z"),e("aW1nW2FsdD0iUmVrbGFtaW5pcyBza3lkZWxpcyJd"),e("aW1nW2FsdD0iRGVkaWt1b3RpLmx0IHNlcnZlcmlhaSJd"),e("aW1nW2FsdD0iSG9zdGluZ2FzIFNlcnZlcmlhaS5sdCJd")],estonian:[e("QVtocmVmKj0iaHR0cDovL3BheTRyZXN1bHRzMjQuZXUiXQ==")],fanboyAnnoyances:["#ac-lre-player",".navigate-to-top","#subscribe_popup",".newsletter_holder","#back-top"],fanboyAntiFacebook:[".util-bar-module-firefly-visible"],fanboyEnhancedTrackers:[".open.pushModal","#issuem-leaky-paywall-articles-zero-remaining-nag","#sovrn_container",'div[class$="-hide"][zoompage-fontsize][style="display: block;"]',".BlockNag__Card"],fanboySocial:["#FollowUs","#meteored_share","#social_follow",".article-sharer",".community__social-desc"],frellwitSwedish:[e("YVtocmVmKj0iY2FzaW5vcHJvLnNlIl1bdGFyZ2V0PSJfYmxhbmsiXQ=="),e("YVtocmVmKj0iZG9rdG9yLXNlLm9uZWxpbmsubWUiXQ=="),"article.category-samarbete",e("ZGl2LmhvbGlkQWRz"),"ul.adsmodern"],greekAdBlock:[e("QVtocmVmKj0iYWRtYW4ub3RlbmV0LmdyL2NsaWNrPyJd"),e("QVtocmVmKj0iaHR0cDovL2F4aWFiYW5uZXJzLmV4b2R1cy5nci8iXQ=="),e("QVtocmVmKj0iaHR0cDovL2ludGVyYWN0aXZlLmZvcnRobmV0LmdyL2NsaWNrPyJd"),"DIV.agores300","TABLE.advright"],hungarian:["#cemp_doboz",".optimonk-iframe-container",e("LmFkX19tYWlu"),e("W2NsYXNzKj0iR29vZ2xlQWRzIl0="),"#hirdetesek_box"],iDontCareAboutCookies:['.alert-info[data-block-track*="CookieNotice"]',".ModuleTemplateCookieIndicator",".o--cookies--container","#cookies-policy-sticky","#stickyCookieBar"],icelandicAbp:[e("QVtocmVmXj0iL2ZyYW1ld29yay9yZXNvdXJjZXMvZm9ybXMvYWRzLmFzcHgiXQ==")],latvian:[e("YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiAxMjBweDsgaGVpZ2h0OiA0MHB4OyBvdmVyZmxvdzogaGlkZGVuOyBwb3NpdGlvbjogcmVsYXRpdmU7Il0="),e("YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiA4OHB4OyBoZWlnaHQ6IDMxcHg7IG92ZXJmbG93OiBoaWRkZW47IHBvc2l0aW9uOiByZWxhdGl2ZTsiXQ==")],listKr:[e("YVtocmVmKj0iLy9hZC5wbGFuYnBsdXMuY28ua3IvIl0="),e("I2xpdmVyZUFkV3JhcHBlcg=="),e("YVtocmVmKj0iLy9hZHYuaW1hZHJlcC5jby5rci8iXQ=="),e("aW5zLmZhc3R2aWV3LWFk"),".revenue_unit_item.dable"],listeAr:[e("LmdlbWluaUxCMUFk"),".right-and-left-sponsers",e("YVtocmVmKj0iLmFmbGFtLmluZm8iXQ=="),e("YVtocmVmKj0iYm9vcmFxLm9yZyJd"),e("YVtocmVmKj0iZHViaXp6bGUuY29tL2FyLz91dG1fc291cmNlPSJd")],listeFr:[e("YVtocmVmXj0iaHR0cDovL3Byb21vLnZhZG9yLmNvbS8iXQ=="),e("I2FkY29udGFpbmVyX3JlY2hlcmNoZQ=="),e("YVtocmVmKj0id2Vib3JhbWEuZnIvZmNnaS1iaW4vIl0="),".site-pub-interstitiel",'div[id^="crt-"][data-criteo-id]'],officialPolish:["#ceneo-placeholder-ceneo-12",e("W2hyZWZePSJodHRwczovL2FmZi5zZW5kaHViLnBsLyJd"),e("YVtocmVmXj0iaHR0cDovL2Fkdm1hbmFnZXIudGVjaGZ1bi5wbC9yZWRpcmVjdC8iXQ=="),e("YVtocmVmXj0iaHR0cDovL3d3dy50cml6ZXIucGwvP3V0bV9zb3VyY2UiXQ=="),e("ZGl2I3NrYXBpZWNfYWQ=")],ro:[e("YVtocmVmXj0iLy9hZmZ0cmsuYWx0ZXgucm8vQ291bnRlci9DbGljayJd"),e("YVtocmVmXj0iaHR0cHM6Ly9ibGFja2ZyaWRheXNhbGVzLnJvL3Ryay9zaG9wLyJd"),e("YVtocmVmXj0iaHR0cHM6Ly9ldmVudC4ycGVyZm9ybWFudC5jb20vZXZlbnRzL2NsaWNrIl0="),e("YVtocmVmXj0iaHR0cHM6Ly9sLnByb2ZpdHNoYXJlLnJvLyJd"),'a[href^="/url/"]'],ruAd:[e("YVtocmVmKj0iLy9mZWJyYXJlLnJ1LyJd"),e("YVtocmVmKj0iLy91dGltZy5ydS8iXQ=="),e("YVtocmVmKj0iOi8vY2hpa2lkaWtpLnJ1Il0="),"#pgeldiz",".yandex-rtb-block"],thaiAds:["a[href*=macau-uta-popup]",e("I2Fkcy1nb29nbGUtbWlkZGxlX3JlY3RhbmdsZS1ncm91cA=="),e("LmFkczMwMHM="),".bumq",".img-kosana"],webAnnoyancesUltralist:["#mod-social-share-2","#social-tools",e("LmN0cGwtZnVsbGJhbm5lcg=="),".zergnet-recommend",".yt.btn-link.btn-md.btn"]}}function cr(e){var t=e===void 0?{}:e,n=t.debug;return le(this,void 0,void 0,function(){var i,c,a,u,v,d;return de(this,function(f){switch(f.label){case 0:return ur()?(i=sr(),c=Object.keys(i),a=(d=[]).concat.apply(d,c.map(function(p){return i[p]})),[4,fr(a)]):[2,void 0];case 1:return u=f.sent(),n&&lr(i,u),v=c.filter(function(p){var l=i[p],_=re(l.map(function(k){return u[k]}));return _>l.length*.6}),v.sort(),[2,v]}})})}function ur(){return pe()||qe()}function fr(e){var t;return le(this,void 0,void 0,function(){var n,i,c,a,d,u,v,d;return de(this,function(f){switch(f.label){case 0:for(n=document,i=n.createElement("div"),c=new Array(e.length),a={},dt(i),d=0;d<e.length;++d)u=_n(e[d]),u.tagName==="DIALOG"&&u.show(),v=n.createElement("div"),dt(v),v.appendChild(u),i.appendChild(v),c[d]=u;f.label=1;case 1:return n.body?[3,3]:[4,De(50)];case 2:return f.sent(),[3,1];case 3:n.body.appendChild(i);try{for(d=0;d<e.length;++d)c[d].offsetParent||(a[e[d]]=!0)}finally{(t=i.parentNode)===null||t===void 0||t.removeChild(i)}return[2,a]}})})}function dt(e){e.style.setProperty("visibility","hidden","important"),e.style.setProperty("display","block","important")}function lr(e,t){for(var n="DOM blockers debug:\n```",i=0,c=Object.keys(e);i<c.length;i++){var a=c[i];n+=` +`.concat(a,":");for(var u=0,v=e[a];u<v.length;u++){var d=v[u];n+=` + `.concat(t[d]?"\u{1F6AB}":"\u27A1\uFE0F"," ").concat(d)}}console.log("".concat(n,"\n```"))}function dr(){for(var e=0,t=["rec2020","p3","srgb"];e<t.length;e++){var n=t[e];if(matchMedia("(color-gamut: ".concat(n,")")).matches)return n}}function vr(){if(vt("inverted"))return!0;if(vt("none"))return!1}function vt(e){return matchMedia("(inverted-colors: ".concat(e,")")).matches}function pr(){if(pt("active"))return!0;if(pt("none"))return!1}function pt(e){return matchMedia("(forced-colors: ".concat(e,")")).matches}var mr=100;function hr(){if(matchMedia("(min-monochrome: 0)").matches){for(var e=0;e<=mr;++e)if(matchMedia("(max-monochrome: ".concat(e,")")).matches)return e;throw new Error("Too high value")}}function gr(){if(Ie("no-preference"))return 0;if(Ie("high")||Ie("more"))return 1;if(Ie("low")||Ie("less"))return-1;if(Ie("forced"))return 10}function Ie(e){return matchMedia("(prefers-contrast: ".concat(e,")")).matches}function wr(){if(mt("reduce"))return!0;if(mt("no-preference"))return!1}function mt(e){return matchMedia("(prefers-reduced-motion: ".concat(e,")")).matches}function yr(){if(ht("reduce"))return!0;if(ht("no-preference"))return!1}function ht(e){return matchMedia("(prefers-reduced-transparency: ".concat(e,")")).matches}function br(){if(gt("high"))return!0;if(gt("standard"))return!1}function gt(e){return matchMedia("(dynamic-range: ".concat(e,")")).matches}var D=Math,ne=function(){return 0};function _r(){var e=D.acos||ne,t=D.acosh||ne,n=D.asin||ne,i=D.asinh||ne,c=D.atanh||ne,a=D.atan||ne,u=D.sin||ne,v=D.sinh||ne,d=D.cos||ne,f=D.cosh||ne,p=D.tan||ne,l=D.tanh||ne,_=D.exp||ne,k=D.expm1||ne,G=D.log1p||ne,N=function(O){return D.pow(D.PI,O)},z=function(O){return D.log(O+D.sqrt(O*O-1))},F=function(O){return D.log(O+D.sqrt(O*O+1))},Y=function(O){return D.log((1+O)/(1-O))/2},K=function(O){return D.exp(O)-1/D.exp(O)/2},$=function(O){return(D.exp(O)+1/D.exp(O))/2},ee=function(O){return D.exp(O)-1},T=function(O){return(D.exp(2*O)-1)/(D.exp(2*O)+1)},se=function(O){return D.log(1+O)};return{acos:e(.12312423423423424),acosh:t(1e308),acoshPf:z(1e154),asin:n(.12312423423423424),asinh:i(1),asinhPf:F(1),atanh:c(.5),atanhPf:Y(.5),atan:a(.5),sin:u(-1e300),sinh:v(1),sinhPf:K(1),cos:d(10.000000000123),cosh:f(1),coshPf:$(1),tan:p(-1e300),tanh:l(1),tanhPf:T(1),exp:_(1),expm1:k(1),expm1Pf:ee(1),log1p:G(10),log1pPf:se(10),powPI:N(-100)}}var Sr="mmMwWLliI0fiflO&1",Ye={default:[],apple:[{font:"-apple-system-body"}],serif:[{fontFamily:"serif"}],sans:[{fontFamily:"sans-serif"}],mono:[{fontFamily:"monospace"}],min:[{fontSize:"1px"}],system:[{fontFamily:"system-ui"}]};function Cr(){return kr(function(e,t){for(var n={},i={},c=0,a=Object.keys(Ye);c<a.length;c++){var u=a[c],v=Ye[u],d=v[0],f=d===void 0?{}:d,p=v[1],l=p===void 0?Sr:p,_=e.createElement("span");_.textContent=l,_.style.whiteSpace="nowrap";for(var k=0,G=Object.keys(f);k<G.length;k++){var N=G[k],z=f[N];z!==void 0&&(_.style[N]=z)}n[u]=_,t.append(e.createElement("br"),_)}for(var F=0,Y=Object.keys(Ye);F<Y.length;F++){var u=Y[F];i[u]=n[u].getBoundingClientRect().width}return i})}function kr(e,t){return t===void 0&&(t=4e3),kt(function(n,i){var c=i.document,a=c.body,u=a.style;u.width="".concat(t,"px"),u.webkitTextSizeAdjust=u.textSizeAdjust="none",Ae()?a.style.zoom="".concat(1/i.devicePixelRatio):pe()&&(a.style.zoom="reset");var v=c.createElement("div");return v.textContent=Ze([],Array(t/20<<0),!0).map(function(){return"word"}).join(" "),a.appendChild(v),e(c,a)},'<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1">')}function Lr(){return navigator.pdfViewerEnabled}function Er(){var e=new Float32Array(1),t=new Uint8Array(e.buffer);return e[0]=1/0,e[0]=e[0]-e[0],t[3]}function Pr(){var e=window.ApplePaySession;if(typeof e?.canMakePayments!="function")return-1;if(xr())return-3;try{return e.canMakePayments()?1:0}catch(t){return Ir(t)}}var xr=Cn;function Ir(e){if(e instanceof Error&&e.name==="InvalidAccessError"&&/\bfrom\b.*\binsecure\b/i.test(e.message))return-2;throw e}function Rr(){var e,t=document.createElement("a"),n=(e=t.attributionSourceId)!==null&&e!==void 0?e:t.attributionsourceid;return n===void 0?void 0:String(n)}var Lt=-1,Et=-2,Tr=new Set([10752,2849,2884,2885,2886,2928,2929,2930,2931,2932,2960,2961,2962,2963,2964,2965,2966,2967,2968,2978,3024,3042,3088,3089,3106,3107,32773,32777,32777,32823,32824,32936,32937,32938,32939,32968,32969,32970,32971,3317,33170,3333,3379,3386,33901,33902,34016,34024,34076,3408,3410,3411,3412,3413,3414,3415,34467,34816,34817,34818,34819,34877,34921,34930,35660,35661,35724,35738,35739,36003,36004,36005,36347,36348,36349,37440,37441,37443,7936,7937,7938]),Ar=new Set([34047,35723,36063,34852,34853,34854,34229,36392,36795,38449]),Nr=["FRAGMENT_SHADER","VERTEX_SHADER"],Fr=["LOW_FLOAT","MEDIUM_FLOAT","HIGH_FLOAT","LOW_INT","MEDIUM_INT","HIGH_INT"],Pt="WEBGL_debug_renderer_info",Or="WEBGL_polygon_mode";function Mr(e){var t,n,i,c,a,u,v=e.cache,d=xt(v);if(!d)return Lt;if(!Rt(d))return Et;var f=It()?null:d.getExtension(Pt);return{version:((t=d.getParameter(d.VERSION))===null||t===void 0?void 0:t.toString())||"",vendor:((n=d.getParameter(d.VENDOR))===null||n===void 0?void 0:n.toString())||"",vendorUnmasked:f?(i=d.getParameter(f.UNMASKED_VENDOR_WEBGL))===null||i===void 0?void 0:i.toString():"",renderer:((c=d.getParameter(d.RENDERER))===null||c===void 0?void 0:c.toString())||"",rendererUnmasked:f?(a=d.getParameter(f.UNMASKED_RENDERER_WEBGL))===null||a===void 0?void 0:a.toString():"",shadingLanguageVersion:((u=d.getParameter(d.SHADING_LANGUAGE_VERSION))===null||u===void 0?void 0:u.toString())||""}}function jr(e){var t=e.cache,n=xt(t);if(!n)return Lt;if(!Rt(n))return Et;var i=n.getSupportedExtensions(),c=n.getContextAttributes(),a=[],u=[],v=[],d=[],f=[];if(c)for(var p=0,l=Object.keys(c);p<l.length;p++){var _=l[p];u.push("".concat(_,"=").concat(c[_]))}for(var k=wt(n),G=0,N=k;G<N.length;G++){var z=N[G],F=n[z];v.push("".concat(z,"=").concat(F).concat(Tr.has(F)?"=".concat(n.getParameter(F)):""))}if(i)for(var Y=0,K=i;Y<K.length;Y++){var $=K[Y];if(!($===Pt&&It()||$===Or&&Dr())){var ee=n.getExtension($);if(!ee){a.push($);continue}for(var T=0,se=wt(ee);T<se.length;T++){var z=se[T],F=ee[z];d.push("".concat(z,"=").concat(F).concat(Ar.has(F)?"=".concat(n.getParameter(F)):""))}}}for(var O=0,Le=Nr;O<Le.length;O++)for(var Se=Le[O],g=0,h=Fr;g<h.length;g++){var C=h[g],b=Vr(n,Se,C);f.push("".concat(Se,".").concat(C,"=").concat(b.join(",")))}return d.sort(),v.sort(),{contextAttributes:u,parameters:v,shaderPrecisions:f,extensions:i,extensionParameters:d,unsupportedExtensions:a}}function xt(e){if(e.webgl)return e.webgl.context;var t=document.createElement("canvas"),n;t.addEventListener("webglCreateContextError",function(){return n=void 0});for(var i=0,c=["webgl","experimental-webgl"];i<c.length;i++){var a=c[i];try{n=t.getContext(a)}catch{}if(n)break}return e.webgl={context:n},n}function Vr(e,t,n){var i=e.getShaderPrecisionFormat(e[t],e[n]);return i?[i.rangeMin,i.rangeMax,i.precision]:[]}function wt(e){var t=Object.keys(e.__proto__);return t.filter(Wr)}function Wr(e){return typeof e=="string"&&!e.match(/[^A-Z0-9_x]/)}function It(){return Ct()}function Dr(){return Ae()||pe()}function Rt(e){return typeof e.getParameter=="function"}function Gr(){var e=qe()||pe();if(!e)return-2;if(!window.AudioContext)return-1;var t=new AudioContext().baseLatency;return t==null?-1:isFinite(t)?t:-3}function Zr(){if(!window.Intl)return-1;var e=window.Intl.DateTimeFormat;if(!e)return-2;var t=e().resolvedOptions().locale;return!t&&t!==""?-3:t}var Hr={fonts:En,domBlockers:cr,fontPreferences:Cr,audio:mn,screenFrame:Jn,canvas:xn,osCpu:Vn,languages:Wn,colorDepth:Dn,deviceMemory:Gn,screenResolution:Zn,hardwareConcurrency:Un,timezone:qn,sessionStorage:Kn,localStorage:$n,indexedDB:er,openDatabase:tr,cpuClass:nr,platform:rr,plugins:Pn,touchSupport:jn,vendor:ir,vendorFlavors:ar,cookiesEnabled:or,colorGamut:dr,invertedColors:vr,forcedColors:pr,monochrome:hr,contrast:gr,reducedMotion:wr,reducedTransparency:yr,hdr:br,math:_r,pdfViewerEnabled:Lr,architecture:Er,applePay:Pr,privateClickMeasurement:Rr,audioBaseLatency:Gr,dateTimeLocale:Zr,webGlBasics:Mr,webGlExtensions:jr};function Xr(e){return on(Hr,e,[])}var Yr="$ if upgrade to Pro: https://fpjs.dev/pro";function Br(e){var t=zr(e),n=Jr(t);return{score:t,comment:Yr.replace(/\$/g,"".concat(n))}}function zr(e){if(qe())return .4;if(pe())return Ue()&&!(Fe()&&Ne())?.5:.3;var t="value"in e.platform?e.platform.value:"";return/^Win/.test(t)?.6:/^Mac/.test(t)?.5:.7}function Jr(e){return _t(.99+.01*e,1e-4)}function Ur(e){for(var t="",n=0,i=Object.keys(e).sort();n<i.length;n++){var c=i[n],a=e[c],u="error"in a?"error":JSON.stringify(a.value);t+="".concat(t?"|":"").concat(c.replace(/([:|\\])/g,"\\$1"),":").concat(u)}return t}function Tt(e){return JSON.stringify(e,function(t,n){return n instanceof Error?tn(n):n},2)}function At(e){return en(Ur(e))}function qr(e){var t,n=Br(e);return{get visitorId(){return t===void 0&&(t=At(this.components)),t},set visitorId(i){t=i},confidence:n,components:e,version:yt}}function Qr(e){return e===void 0&&(e=50),Yt(e,e*2)}function Kr(e,t){var n=Date.now();return{get:function(i){return le(this,void 0,void 0,function(){var c,a,u;return de(this,function(v){switch(v.label){case 0:return c=Date.now(),[4,e()];case 1:return a=v.sent(),u=qr(a),(t||i?.debug)&&console.log("Copy the text below to get the debug data:\n\n```\nversion: ".concat(u.version,` +userAgent: `).concat(navigator.userAgent,` +timeBetweenLoadAndGet: `).concat(c-n,` +visitorId: `).concat(u.visitorId,` +components: `).concat(Tt(a),"\n```")),[2,u]}})})}}}function $r(){if(!(window.__fpjs_d_m||Math.random()>=.001))try{var e=new XMLHttpRequest;e.open("get","https://m1.openfpcdn.io/fingerprintjs/v".concat(yt,"/npm-monitoring"),!0),e.send()}catch(t){console.error(t)}}function ei(e){var t;return e===void 0&&(e={}),le(this,void 0,void 0,function(){var n,i,c;return de(this,function(a){switch(a.label){case 0:return(!((t=e.monitoring)!==null&&t!==void 0)||t)&&$r(),n=e.delayFallback,i=e.debug,[4,Qr(n)];case 1:return a.sent(),c=Xr({cache:{},debug:i}),[2,Kr(c,i)]}})})}var Ge={load:ei,hashComponents:At,componentsToDebugString:Tt};var R=(function(){var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-",t="",n="",i=0,c=!1,a=[],u=!1,v=4,d=80,f="",p=0,l=20,_=[],k=function(){return[]},G=function(){},N=function(){return{}},z=!1;function F(r){_.length>=l&&_.shift(),_.push(r)}function Y(){if(_.length){var r=N();if(!(!r.id||parseInt(r.id,10)<=0))for(;_.length;){var s=_.shift(),o="action=slimtrack&id="+r.id+s;I(o,!0,{priority:"normal"})}}}var K="slimstat_offline_queue";function $(r){r=(r||"").replace(/\r\n/g,` +`);for(var s="",o=0;o<r.length;o++){var m=r.charCodeAt(o);m<128?s+=String.fromCharCode(m):m<2048?s+=String.fromCharCode(m>>6|192,m&63|128):s+=String.fromCharCode(m>>12|224,m>>6&63|128,m&63|128)}return s}function ee(r){var s="",o=0;for(r=$(r);o<r.length;){var m=r.charCodeAt(o++),S=r.charCodeAt(o++),w=r.charCodeAt(o++),P=m>>2,x=(m&3)<<4|S>>4,E=(S&15)<<2|w>>6,L=w&63;isNaN(S)?E=L=64:isNaN(w)&&(L=64),s+=e.charAt(P)+e.charAt(x)+e.charAt(E)+e.charAt(L)}return s}function T(r){if(r==null)return!0;var s=typeof r;return s==="boolean"?!r:s==="number"?isNaN(r)||r===0:s==="string"||Array.isArray(r)?r.length===0:s==="object"?Object.keys(r).length===0:!1}function se(r,s){if(!r||!s||!s.length)return!1;for(var o=0;o<s.length;o++)if(r.indexOf(s[o].trim())!==-1)return!0;return!1}function O(r){var s="; "+document.cookie,o=s.split("; "+r+"=");return o.length===2?o.pop().split(";").shift():""}function Le(r,s,o){r&&(r.addEventListener?r.addEventListener(s,o,!1):r.attachEvent?r.attachEvent("on"+s,o):r["on"+s]=o)}function Se(){var r=(window.performance||{}).timing||{};return!r.responseEnd||!r.connectEnd?0:r.responseEnd-r.connectEnd}function g(){var r=(window.performance||{}).timing||{};return!r.loadEventEnd||!r.responseEnd?0:r.loadEventEnd-r.responseEnd}function h(r,s,o){return r&&r[s]&&r[s].value!==void 0?r[s].value:o}function C(){var r=document.querySelector('meta[name="slimstat-params"]');if(r)try{window.SlimStatParams=JSON.parse(r.getAttribute("content"))||{}}catch{}else for(var s=document.querySelectorAll("script"),o=s.length-1;o>=0;o--){var m=s[o].textContent.match(/var\s+SlimStatParams\s*=\s*({[\s\S]*?});/);if(m)try{window.SlimStatParams=new Function("return "+m[1])()||{};break}catch{}}return N()}function b(r){try{if(r&&r.visitorId){t=r.visitorId;return}t=""}catch{t=""}}function y(r){var s=r&&typeof r=="object"&&!Array.isArray(r),o=[0,0];try{s&&(o=h(r,"screenResolution",[0,0])),(!o||o[0]===0)&&window.screen&&(o=[window.screen.width||0,window.screen.height||0])}catch{o=[0,0]}var m=0;try{s&&(m=h(r,"timezoneOffset",0)),m===0&&!s&&(m=new Date().getTimezoneOffset())}catch{m=0}return"&sw="+o[0]+"&sh="+o[1]+"&bw="+window.innerWidth+"&bh="+window.innerHeight+"&sl="+Se()+"&pp="+g()+"&fh="+t+"&tz="+m}function I(r,s,o){if(T(r))return!1;o=o||{};var m={payload:r,useBeacon:s,opts:o,attempts:0},S=a.some(function(P){return P.payload===r});if(S)return!1;if(a.length>d)for(var w=a.length-1;w>=0&&a.length>d;w--)a[w].opts.priority!=="high"&&a.splice(w,1);return o.immediate||o.priority==="high"?(!a.length||a[0].payload!==r)&&a.unshift(m):a.push(m),u||H(),!0}function H(){if(!(u||!a.length)){var r=a.shift();if(r){u=!0;var s=function(o){if(!o&&r)if(r.attempts=(r.attempts||0)+1,r.attempts<v){var m=500*Math.pow(2,r.attempts);setTimeout(function(){a.unshift(r)},m)}else R.store_offline(r.payload),r.opts&&typeof r.opts.onComplete=="function"&&r.opts.onComplete(!1);else r.opts&&typeof r.opts.onComplete=="function"&&r.opts.onComplete(!!o);u=!1,setTimeout(H,50)};M(r,s)}}}function M(r,s){var o=N(),m=r.payload,S=r.useBeacon,w=T(o.id)||isNaN(parseInt(o.id,10))||parseInt(o.id,10)<=0,P=["rest","ajax","adblock_bypass"],x={rest:o.ajaxurl_rest,ajax:o.ajaxurl_ajax,adblock_bypass:o.ajaxurl_adblock},E=o.transport,L=[E].concat(P.filter(function(W){return W!==E}));function j(W,B,J){J=J||{useNonce:!0};var q;try{q=new XMLHttpRequest}catch{return B&&B(),!1}q.open("POST",W,!0),q.setRequestHeader("Content-type","application/x-www-form-urlencoded"),q.setRequestHeader("X-Requested-With","XMLHttpRequest"),J.useNonce&&o.wp_rest_nonce&&q.setRequestHeader("X-WP-Nonce",o.wp_rest_nonce),q.withCredentials=!0,q.onreadystatechange=function(){if(q.readyState===4){if(q.status===403&&J.useNonce&&o.wp_rest_nonce){j(W,B,{useNonce:!1});return}if(q.status===200){var Z=parseInt(q.responseText,10);if(!isNaN(Z)&&Z>0){o.id=q.responseText;try{window.slimstatPageviewTracked=!0}catch{}Y()}if(w&&(isNaN(Z)||Z<=0)){B&&B();return}s(!0)}else B&&B()}};try{q.send(m)}catch{B&&B()}return!0}function V(W){if(W>=L.length)return s(!1),!1;var B=L[W],J=x[B];if(!J)return V(W+1);if(S&&navigator.sendBeacon&&W===0){var q=navigator.sendBeacon(J,m);return q?(s(!0),!0):V(W+1)}return j(J,function(){V(W+1)},{useNonce:!0})}V(0)}function A(r,s,o){var m=N();if(T(m.id)||isNaN(parseInt(m.id,10))||parseInt(m.id,10)<=0){try{var S=X(r,s);F(S)}catch{}return!1}if(!r||T(r.type)||r.type==="focus")return!1;o=typeof o=="boolean"?o:!0;var w=r.target||r.srcElement;if(!w)return!1;var P={};T(s)||(P.note=s);var x="";if((function(){if(w.nodeName){var Oe=w.nodeName.toLowerCase();if(Oe==="input"||Oe==="button"){for(var Q=w.parentNode;Q&&Q.nodeName&&Q.nodeName.toLowerCase()!=="form";)Q=Q.parentNode;Q&&Q.action&&(x=Q.action);return}if(!w.href||typeof w.href!="string"){for(var Q=w.parentNode;Q&&Q.nodeName&&!Q.href;)Q=Q.parentNode;Q&&(Q.hash&&Q.hostname===location.hostname?x=Q.hash:Q.href&&(x=Q.href))}else w.hash?x=w.hash:x=w.href}})(),typeof w.getAttribute=="function"){w.textContent&&(P.text=w.textContent);var E=w.getAttribute("value");E&&(P.value=E);var L=w.getAttribute("title");L&&(P.title=L);var j=w.getAttribute("id");j&&(P.id=j)}P.type=r.type,r.type==="keypress"?P.key=String.fromCharCode(parseInt(r.which,10)):r.type==="mousedown"&&(P.button=r.which===1?"left":r.which===2?"middle":"right");var V=m.dnt?m.dnt.split(","):[];if(x&&V.length&&se(x,V))return!1;if(V.length&&w.className&&typeof w.className=="string"){var W=w.className.split(" ");if(W.some(function(ke){return V.indexOf(ke)!==-1}))return!1}if(V.length&&w.attributes&&w.attributes.rel&&w.attributes.rel.value&&se(w.attributes.rel.value,V))return!1;var B="0,0";!T(r.pageX)&&!T(r.pageY)?B=r.pageX+","+r.pageY:T(r.clientX)||(B=r.clientX+(document.body.scrollLeft||0)+(document.documentElement.scrollLeft||0)+","+(r.clientY+(document.body.scrollTop||0)+(document.documentElement.scrollTop||0)));var J=x?"&fh="+t:"",q="&res="+ee(x)+"&pos="+B+"&no="+ee(JSON.stringify(P))+J,Z="action=slimtrack&id="+m.id+q,Ee=Date.now();if(Z===f&&Ee-p<1e3)return!1;f=Z,p=Ee;var Re=I(Z,o);if(Re)try{window.__slimstatHasInteraction=!0}catch{}return Re}function X(r,s){var o=r&&(r.target||r.srcElement)||{},m="";try{o.href&&(m=o.href)}catch{}var S={type:r?r.type:"unknown"};s&&(S.note=s);var w="0,0";return r&&!T(r.pageX)&&!T(r.pageY)&&(w=r.pageX+","+r.pageY),"&res="+ee(m)+"&pos="+w+"&no="+ee(JSON.stringify(S))}var ue={},ae=null,te="slimstat_consent_upgrade_state",ge="slimstat_consent_upgrade_ts";function ye(r){try{return sessionStorage.getItem(r)||""}catch{return window[r]||""}}function fe(r,s){try{s===""||s===null||typeof s>"u"?sessionStorage.removeItem(r):sessionStorage.setItem(r,s)}catch{s===""||s===null||typeof s>"u"?delete window[r]:window[r]=s}}function me(){fe(te,"pending"),fe(ge,Date.now().toString())}function he(r){r?(fe(te,"done"),fe(ge,Date.now().toString())):(fe(te,""),fe(ge,""))}function Nt(){return ye(te)==="done"}function Ft(r){if(r===!0)return me(),!0;var s=ye(te);if(s==="done")return!1;if(s==="pending"){var o=parseInt(ye(ge)||"0",10);if(Date.now()-o<5e3)return!1}return me(),!0}function Ot(r){r=r||{};var s=r.force===!0;if(!Ft(s))return!1;var o={isConsentRetry:!0,consentUpgrade:!0};return r.consent&&(o.consent=r.consent),r.consentNonce&&(o.consentNonce=r.consentNonce),R._send_pageview(o),!0}function be(r){return typeof r=="function"}function ie(r){return r!==null&&typeof r=="object"}function Qe(r){if(!r)return null;try{var s=r.replace(/([.$?*|{}()\[\]\\\/\+^])/g,"\\$1"),o="(?:^|;)\\s*"+s+"=([^;]*)",m=document.cookie.match(o);return m?decodeURIComponent(m[1]):null}catch{return null}}function Mt(r){try{if(be(window.rcb))try{var s=window.rcb("consent",r);if(s===!0||s===!1)return!!s;if(ie(s)&&"cookie"in s)return!!s.cookie;if(ie(s)&&"consent"in s)return!!s.consent}catch{}if(ie(window.RCB)&&ie(window.RCB.consent)&&be(window.RCB.consent.get)){var o=window.RCB.consent.get(r);if(o===!0||o===!1)return!!o;if(ie(o)&&"cookie"in o)return!!o.cookie;if(ie(o)&&"consent"in o)return!!o.consent}if(ie(window.rcbConsentManager)&&be(window.rcbConsentManager.getUserDecision)){var m=window.rcbConsentManager.getUserDecision();if(m&&m.decision){if(m.decision==="all")return!0;if(typeof m.decision=="object"){var S=m.decision[r];if(typeof S=="boolean")return S;if(Array.isArray(S))return S.length>0}}}var w=window.realCookieBanner||window.RealCookieBanner||null;if(ie(w)&&ie(w.consent)&&be(w.consent.get)){var P=w.consent.get(r);if(P===!0||P===!1)return!!P;if(ie(P)&&"cookie"in P)return!!P.cookie;if(P)return!0}var x=window.__rcb||window.__RCB||null;if(ie(x)&&x.consent){var E=x.consent[r];if(typeof E=="boolean")return E;if(Array.isArray(E))return E.length>0}for(var L=["real_cookie_banner","rcb_consent","rcb_acceptance","real_cookie_consent","rcb-consent"],j=0;j<L.length;j++){var V=Qe(L[j]);if(V)try{var W=JSON.parse(V);if(W){if(typeof W[r]=="boolean")return W[r];if(typeof W.consent=="boolean")return W.consent;if(typeof W[r]=="object"&&W[r].cookie!==void 0)return!!W[r].cookie}}catch{var B=V.toLowerCase();if(V.indexOf(r)!==-1||V==="1"||B==="true"||B==="all"||B==="accepted")return!0}}}catch{}return null}function Ke(r){try{if(be(window.wp_has_service_consent))try{var s=window.wp_has_service_consent(r);if(s)return!0;if(be(window.wp_is_service_denied)&&window.wp_is_service_denied(r))return!1}catch{}if(be(window.wp_has_consent))try{var o=window.wp_has_consent(r);return!!o}catch{}var m=window.wpConsent||window.WPConsent||null;if(ie(m)&&be(m.get)){var S=m.get(r);if(S===!0||S===!1)return!!S}}catch{}return null}function jt(r,s){try{var o=r||"slimstat_gdpr_consent",m=Qe(o);if(!m)return null;if(m==="accepted")return!0;if(m==="denied")return!1;try{var S=JSON.parse(m);if(S&&S[s]!==void 0)return!!S[s]}catch{}return m.length>0}catch{return null}}function Vt(r){var s={functional:"deny",statistics:"deny",statistics_anonymous:"deny",marketing:"deny"};if(typeof r=="boolean")return s.statistics=r?"allow":"deny",s;if(typeof r=="string")return r==="accepted"||r==="allow"||r==="grant"?s.statistics="allow":(r==="denied"||r==="deny"||r==="revoke")&&(s.statistics="deny"),s;if(!ie(r)&&!Array.isArray(r))return s;var o=r;if(Array.isArray(r)&&(o={allowed:r}),Array.isArray(o.allowed)){for(var m=0;m<o.allowed.length;m++){var S=o.allowed[m];s.hasOwnProperty(S)&&(s[S]="allow")}return s}if(Array.isArray(o.denied))for(var w=0;w<o.denied.length;w++){var P=o.denied[w];s.hasOwnProperty(P)&&(s[P]="deny")}for(var x=["functional","statistics","statistics_anonymous","marketing"],E=0;E<x.length;E++){var L=x[E];if(o[L]!==void 0)typeof o[L]=="boolean"?s[L]=o[L]?"allow":"deny":typeof o[L]=="string"&&(s[L]=["allow","accepted","grant","true"].indexOf(o[L])!==-1?"allow":"deny");else if(o.groups&&o.groups[L]!==void 0){var j=o.groups[L];typeof j=="boolean"?s[L]=j?"allow":"deny":typeof j=="string"&&(s[L]=["allow","accepted","grant","true"].indexOf(j)!==-1?"allow":"deny")}else if(o.decision!==void 0){if(o.decision==="all")s[L]="allow";else if(ie(o.decision)&&o.decision[L]!==void 0){var V=o.decision[L];typeof V=="boolean"?s[L]=V?"allow":"deny":typeof V=="string"&&(s[L]=["allow","accepted","grant","true"].indexOf(V)!==-1?"allow":"deny")}}}return s}function Wt(r,s,o){try{var m=N(),S=m.wp_rest_nonce||"",w="";if(m.resturl)w=m.resturl;else if(typeof window.wpApiSettings<"u"&&window.wpApiSettings.root)w=window.wpApiSettings.root;else{var P=window.location.origin;if(m.baseurl&&m.baseurl!=="/"){var x=m.baseurl.replace(/\/$/,"");w=P+x+"/wp-json/"}else w=P+"/wp-json/"}w&&w.charAt(w.length-1)!=="/"&&(w+="/");var E=w+"slimstat/v1/consent-change",L={source:r,parsed:s,ts:Date.now(),mode:{gdprEnabled:m.gdpr_enabled!=="off",anonymousTrackingEnabled:m.anonymous_tracking==="on"},nonce:S};if(o&&(L.pageview_id=String(o)),typeof window.fetch=="function")fetch(E,{method:"POST",headers:{"Content-Type":"application/json","X-WP-Nonce":S},credentials:"same-origin",body:JSON.stringify(L)}).then(function(V){if(V.ok)return V.json()}).catch(function(){});else{var j=new XMLHttpRequest;j.open("POST",E,!0),j.setRequestHeader("Content-Type","application/json"),j.setRequestHeader("X-WP-Nonce",S),j.onload=function(){if(j.status>=200&&j.status<300)try{var V=JSON.parse(j.responseText)}catch{}},j.onerror=function(){},j.send(JSON.stringify(L))}}catch{}}function $e(r){if(r)try{var s=new CustomEvent("slimstat:consent:updated",{detail:r});document.dispatchEvent(s)}catch{try{var o=document.createEvent("CustomEvent");o.initCustomEvent("slimstat:consent:updated",!0,!0,r),document.dispatchEvent(o)}catch{}}}function Ce(r){if(r){var s=r.allowed+"|"+r.mode+"|"+r.reason;if(s!==ae){ae=s;var o=N(),m=o.id&&parseInt(o.id,10)>0;m&&$e(r)}}}function et(r,s){s=s||{};var o=r||{},m=o.gdpr_enabled!=="off",S=o.anonymous_tracking==="on",w=o.set_tracker_cookie==="on",P=o.anonymize_ip==="on",x=o.hash_ip==="on",E=o.consent_integration||"",L=o.consent_level_integration||"statistics";try{var j=o.respect_dnt==="on";if(j&&typeof navigator<"u"&&(navigator.doNotTrack==="1"||navigator.doNotTrack==="yes")){var V={allowed:!1,mode:"blocked",reason:"dnt"};return Ce(V),V}}catch{}if(!m){if(S){var W={allowed:!0,mode:"anonymous",reason:"gdpr_disabled_anonymous_mode"};return Ce(W),W}var B={allowed:!0,mode:"full",reason:"gdpr_disabled"};return Ce(B),B}var J=!!(w||!P&&!x),q=J||S,Z=null;if(q){if(E==="wp_consent_api"||E==="wpconsent"||E==="wp_consent"||E===""){var Ee=Ke(L);Ee!==null&&(Z=Ee),Z===null&&o.server_side_consent!==void 0&&(Z=!!o.server_side_consent),E===""&&Z===null&&(Z=!0)}if(Z===null&&(E==="real_cookie_banner"||E==="rcb"||E==="realcookie")){var Re=Mt(L);if(Re!==null)Z=Re;else if(s.isConsentRetry){var ke=Ke(L);ke!==null&&(Z=ke)}}if(Z===null&&(E==="slimstat_banner"||E==="slimstat")){var Oe=o.gdpr_cookie_name||"slimstat_gdpr_consent",Q=jt(Oe,L);Q!==null&&(Z=Q)}Z===null&&(S?Z=!0:J&&E&&E!==""?Z=!1:Z=!0),Z!==!0&&Nt()&&(Z=!0)}if(S){var tt=Z===!0,nt={allowed:!0,mode:tt?"full":"anonymous",reason:tt?"anonymous_mode_consented":"anonymous_mode"};return Ce(nt),nt}if(!J){var rt={allowed:!0,mode:"full",reason:"no_pii"};return Ce(rt),rt}if(Z===!1){var it={allowed:!1,mode:"blocked",reason:"cmp_denied"};return Ce(it),it}var at={allowed:!0,mode:"full",reason:"cmp_allowed"};return Ce(at),at}function Dt(r){if(!T(r.id)&&parseInt(r.id,10)>0)return"action=slimtrack&id="+r.id;var s="action=slimtrack&ref="+ee(document.referrer)+"&res="+ee(window.location.href);return T(r.ci)||(s+="&ci="+r.ci),s}function Gt(r){r=r||{},C();var s="slimstat_pageview_"+(r.isNavigation?"nav":"init")+"_"+(r.isConsentRetry?"retry":"normal");if(!(window.sendingSlimStatPageview||window[s])){window.sendingSlimStatPageview=!0,window[s]=!0;var o=N(),m="";r.consentUpgrade&&(m="&consent_upgrade=1",o.id&&(m+="&pageview_id="+encodeURIComponent(o.id)));var S=et(o,{isNavigation:!!r.isNavigation,isConsentRetry:!!r.isConsentRetry});if(!S.allowed){window.sendingSlimStatPageview=!1,delete window[s];return}r.consentUpgrade&&S.mode==="full"&&(m="&consent_upgrade=1",o.id&&(m+="&pageview_id="+encodeURIComponent(o.id)));var w=r.isNavigation||!1,P=r.isConsentRetry||!1;if(!w&&!P&&!T(o.id)&&parseInt(o.id,10)>0){window.sendingSlimStatPageview=!1,delete window[s];return}w&&(o.id=null);var x=Dt(o);if(!x){window.sendingSlimStatPageview=!1,delete window[s];return}if(z){window.sendingSlimStatPageview=!1,delete window[s];return}var E=Date.now();if(x===n&&E-i<150){window.sendingSlimStatPageview=!1,delete window[s];return}n=x,i=E;var L=R.empty(o.id)||parseInt(o.id,10)<=0,j=!L;if(c&&L){window.sendingSlimStatPageview=!1,delete window[s];return}c=L,z=!0;var V=function(){setTimeout(function(){c=!1,z=!1,window.sendingSlimStatPageview=!1,delete window[s]},200)},W=function(J){r.consentUpgrade&&handleConsentUpgradeResult(!!J),V()};r.consent&&(r.consent==="accepted"||r.consent==="denied")&&(m+="&banner_consent="+encodeURIComponent(r.consent),r.consentNonce&&(m+="&banner_consent_nonce="+encodeURIComponent(r.consentNonce)));var B=function(){if(S.mode==="anonymous"){b(null),I(x+y({})+m,j,{immediate:T(o.id),onComplete:W});return}try{var J=null;typeof Ge<"u"&&Ge.load&&(J=Ge.load()),J&&typeof J.then=="function"?J.then(function(q){b(q),I(x+y(q.components||{})+m,j,{immediate:T(o.id),onComplete:W})}).catch(function(){b(null),I(x+y({})+m,j,{immediate:T(o.id),onComplete:W})}):(b(null),I(x+y({})+m,j,{immediate:T(o.id),onComplete:W}))}catch{b(null),I(x+y({})+m,j,{immediate:T(o.id),onComplete:W})}};window.requestIdleCallback?window.requestIdleCallback(B):setTimeout(B,250)}}function Zt(r){try{var s=k();s.push({p:r,t:Date.now()}),G(s)}catch{}}function Ht(){try{var r=k();if(!r.length)return;var s=N();if(!s.id||parseInt(s.id,10)<=0)return;for(var o=5,m=0,S=[],w=0;w<r.length&&m<o;w++){var P=r[w];if(P&&P.p){var x=P.p;x.indexOf("id=pending")!==-1&&(x=x.replace("id=pending","id="+s.id)),I(x,!1,{priority:"normal"})&&(S.push(w),m++)}}if(S.length>0){for(var E=S.length-1;E>=0;E--)r.splice(S[E],1);G(r)}}catch{}}return{base64_key_str:e,get fingerprint_hash(){return t},set fingerprint_hash(r){t=r},utf8_encode:$,base64_encode:ee,get_page_performance:g,get_server_latency:Se,add_event:Le,in_array:se,empty:T,get_cookie:O,send_to_server:I,ss_track:A,init_fingerprint_hash:b,get_slimstat_data:y,get_component_value:h,store_offline:Zt,flush_offline_queue:Ht,consent:{checkAllowed:et,emit:$e,normalize:Vt,sendChange:Wt},requestConsentUpgrade:Ot,_extract_params:C,_send_pageview:Gt,_assign_runtime_helpers:function(r){_=r.pendingInteractions,k=r.loadOfflineQueue,G=r.saveOfflineQueue,N=r.currentSlimStatParams,z=r.pageviewInProgress}}})();window.SlimStat=R;Element.prototype.matches||(Element.prototype.matches=Element.prototype.matchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector||Element.prototype.oMatchesSelector||Element.prototype.webkitMatchesSelector||function(e){for(var t=(this.document||this.ownerDocument).querySelectorAll(e),n=t.length;--n>=0&&t.item(n)!==this;);return n>-1});String.prototype.trim||(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")});Array.isArray||(Array.isArray=function(e){return Object.prototype.toString.call(e)==="[object Array]"});window.requestIdleCallback||(window.requestIdleCallback=function(e){return setTimeout(e,250)});(function(){var t=[],n="slimstat_offline_queue",i=!1;function c(g){return typeof g=="function"}function a(g){return g!==null&&typeof g=="object"}function u(g){if(!g)return null;try{var h=g.replace(/([.$?*|{}()\[\]\\\/\+^])/g,"\\$1"),C="(?:^|;)\\s*"+h+"=([^;]*)",b=document.cookie.match(C);return b?decodeURIComponent(b[1]):null}catch{return null}}function v(){try{var g=localStorage.getItem(n);if(!g)return[];var h=JSON.parse(g);return Array.isArray(h)?h:[]}catch{return[]}}function d(g){try{localStorage.setItem(n,JSON.stringify(g.slice(-200)))}catch{}}function f(){return window.SlimStatParams||(window.SlimStatParams={}),window.SlimStatParams}R._assign_runtime_helpers({pendingInteractions:t,loadOfflineQueue:v,saveOfflineQueue:d,currentSlimStatParams:f,pageviewInProgress:i});var p=R.requestConsentUpgrade||function(){return!1},l={},_={},k=!1,G="",N=0,z=1e3;try{typeof window.__slimstatHasInteraction>"u"&&(window.__slimstatHasInteraction=!1)}catch{}try{typeof window.sendingSlimStatPageview>"u"&&(window.sendingSlimStatPageview=!1),typeof window.slimstatPageviewTracked>"u"&&(window.slimstatPageviewTracked=!1)}catch{}function F(g){var h=f();if(!(!h.id||parseInt(h.id,10)<=0||l[h.id]||_[h.id])){var C=Date.now();if(!(k||g===G&&C-N<z)){k=!0,G=g,N=C,_[h.id]=!0;var b="action=slimtrack&id="+h.id+(g?"&fv="+encodeURIComponent(g):"");R.send_to_server(b,!0,{priority:"high",immediate:!1}),l[h.id]=!0,setTimeout(function(){delete _[h.id],k=!1},120)}}}var Y=JSON.stringify(f()),K=new MutationObserver(function(){var g=f();if(R.empty(g.id)||parseInt(g.id,10)<=0){R._extract_params();var h=JSON.stringify(f());h!==Y&&(Y=h)}});if(K.observe(document.head,{childList:!0,subtree:!0}),K.observe(document.body,{childList:!0,subtree:!0}),R.add_event(window,"load",function(){R._extract_params(),R._send_pageview(),setTimeout(function(){try{navigator.onLine!==!1&&R.flush_offline_queue()}catch{}},500)}),document.addEventListener("wp_listen_for_consent_change",function(g){try{var h=g&&g.detail||{},C=f(),b=C.consent_level_integration||"statistics",y="slimstatConsentRetried_"+b;h[b]&&h[b]==="allow"&&(!window[y]||window[y]===!1)&&(window[y]=!0,R._send_pageview({consentUpgrade:!0}))}catch{}}),typeof window.wp_listen_for_consent_change=="function")try{window.wp_listen_for_consent_change(function(g){var h=f(),C=h.consent_level_integration||"statistics",b="slimstatConsentRetried_"+C;g===C&&(!window[b]||window[b]===!1)&&(window[b]=!0,R._send_pageview({consentUpgrade:!0}))})}catch{}document.addEventListener("wp_consent_type_defined",function(){try{var g=f(),h=g.consent_level_integration||"statistics",C="slimstatConsentRetried_"+h;window[C]||(typeof window.wp_has_consent=="function"?window.wp_has_consent(h)&&(window[C]=!0,R._send_pageview({consentUpgrade:!0})):(window[C]=!0,R._send_pageview({consentUpgrade:!0})))}catch{}}),document.addEventListener("wp_consent_change",function(g){if(g.detail&&g.detail.category){var h=g.detail.category,C=f(),b=C.consent_level_integration||"statistics",y="slimstatConsentRetried_"+b,I=window[y]||!1,H=!I&&h===b&&(!C.id||parseInt(C.id,10)<=0);if(H){if(typeof window.wp_has_consent=="function"&&!window.wp_has_consent(b))return;window[y]=!0,R._send_pageview({consentUpgrade:!0})}if(h===b)try{var M=!1;typeof window.wp_has_consent=="function"?M=window.wp_has_consent(b):g.detail.consent!==void 0&&(M=g.detail.consent===!0||g.detail.consent==="allow"),M||markConsentUpgradeDone(!1);var A=normalizeConsent({statistics:M?"allow":"deny"}),X=null;C.id&&parseInt(C.id,10)>0&&(X=parseInt(C.id,10)),sendConsentChangeToServer("wp_consent_api",A,X)}catch{}}});function $(g){var h=f(),C=h.consent_level_integration||"statistics",b=h.consent_integration||"";if(typeof window.wp_has_consent=="function")try{var y=window.wp_has_consent(C);if(!y)return}catch{return}if(b==="real_cookie_banner"||b==="rcb"||b==="realcookie"){var I=detectRealCookieBannerConsent(C);if(I===!1)return}p(g||{})}(function(){document.addEventListener("cmplz_enable_category",function(y){var I=f(),H=I.consent_level_integration||"statistics",M=y&&y.detail&&(y.detail.category||y.detail)||"";M===H&&$()}),document.addEventListener("cmplz_event_status",function(y){var I=f(),H=I.consent_level_integration||"statistics",M=y&&y.detail||{},A=M.category||M.type||"",X=M.status==="allow"||M.enabled===!0;A===H&&X&&$()});var h=null,C=0;function b(y){var I=Date.now(),H=f(),M=H.consent_integration||"";if(!(M!=="real_cookie_banner"&&M!=="rcb"&&M!=="realcookie")){var A=H.consent_level_integration||"statistics",X=!1,ue=null;if(y&&y.detail){if(y.detail.consent&&A in y.detail.consent){var ae=y.detail.consent[A];typeof ae=="boolean"?(X=ae,ue=y.detail.consent):ae&&ae.cookie!==null&&(X=!0,ue=y.detail.consent)}else if(y.detail.button&&(y.detail.button==="accept_all"||y.detail.button==="accept_essentials"||y.detail.button==="save")){var te=R.consent.checkAllowed(H,{});X=te&&te.allowed&&te.mode==="full",y.detail.consent&&(ue=y.detail.consent)}}!X&&typeof window.wp_has_consent=="function"&&(X=!!window.wp_has_consent(A));try{var ge=normalizeConsent(ue||{statistics:X}),ye=null;H.id&&parseInt(H.id,10)>0&&(ye=parseInt(H.id,10)),sendConsentChangeToServer("real_cookie_banner",ge,ye)}catch{}if(!X){var te=R.consent.checkAllowed(H,{});X=te&&te.allowed&&te.mode==="full"}if(X){clearTimeout(h);var fe=I-C,me=fe<100?100-fe:0;h=setTimeout(function(){var he=f();!he.id||parseInt(he.id,10)<=0?$():R.requestConsentUpgrade()},me),C=I}}}document.addEventListener("RealCookieBannerConsentChanged",b),document.addEventListener("rcb-consent-changed",b),document.addEventListener("rcb-consent-update",b),document.addEventListener("rcb-consent-saved",b),document.addEventListener("cookieyes_consent_update",function(){setTimeout($,50)}),document.addEventListener("cookieyes_preferences_update",function(){setTimeout($,50)}),document.addEventListener("cli_consent_update",function(){setTimeout($,50)})})(),R.add_event(document,"visibilitychange",function(){var g=f();document.visibilityState==="hidden"&&g.id&&parseInt(g.id,10)>0&&T("visibility")}),R.add_event(window,"pagehide",function(){var g=f();g.id&&parseInt(g.id,10)>0&&T("pagehide")}),R.add_event(window,"beforeunload",function(){var g=f();g.id&&parseInt(g.id,10)>0&&T("beforeunload")});var ee=null;function T(g){var h=f();!h.id||l[h.id]||(ee&&clearTimeout(ee),ee=setTimeout(function(){F(g)},50))}R.add_event(window,"online",function(){R.flush_offline_queue(),flushPendingInteractions()}),R.add_event(window,"beforeunload",function(){var g=f();if((!g.id||parseInt(g.id,10)<=0)&&t.length>0){var h=v();t.forEach(function(C){var b="action=slimtrack&id=pending"+C;h.push({p:b,t:Date.now()})}),d(h),t.length=0}});function se(){R.add_event(document.body,"click",function(g){for(var h=g.target;h&&h!==document.body&&!(h.hasAttribute&&h.hasAttribute("data-consent"));){if(h.matches&&h.matches("a,button,input,area")){R.ss_track(g,null,null);break}h=h.parentNode}})}function O(){if(R.add_event(document,"wp-interactivity:navigate",function(){if(!i){var b=window.location.pathname,y=window.location.search;setTimeout(function(){var I=window.location.pathname,H=window.location.search;if(I!==b||H!==y){var M=f();M.id&&parseInt(M.id,10)>0&&T("navigation"),R._send_pageview({isNavigation:!0})}},150)}}),window.history&&history.pushState){var g=history.pushState,h=history.replaceState,C=function(b){var y=window.location.pathname,I=window.location.search,H=b?h:g,M=Array.prototype.slice.call(arguments,1),A=H.apply(this,M);return setTimeout(function(){var X=window.location.pathname,ue=window.location.search;if(X!==y||ue!==I){var ae=f();ae.id&&parseInt(ae.id,10)>0&&T("history"),R._send_pageview({isNavigation:!0})}},150),A};history.pushState=function(){var b=Array.prototype.slice.call(arguments);return b.unshift(!1),C.apply(this,b)},history.replaceState=function(){var b=Array.prototype.slice.call(arguments);return b.unshift(!0),C.apply(this,b)},R.add_event(window,"popstate",function(){i||setTimeout(function(){f().id=null,R._send_pageview({isNavigation:!0})},150)})}}se(),O();function Le(){var g=["RCB/OptIn","RCB/OptIn/All","cookieyes_consent_update","cookieyes_preferences_update","cli_consent_update","wp_listen_load","wp_consent_type_functional","wp_consent_type_statistics","slimstat_banner_consent"];g.forEach(function(h){document.addEventListener(h,function(C){p(C)})}),document.addEventListener("slimstat:consent:updated",function(h){h&&h.detail&&h.detail.allowed&&h.detail.mode==="full"&&p()}),R.requestConsentUpgrade=p}function Se(){var g=!1;function h(){if(!g){var b=f();if(!(!b||b.use_slimstat_banner!=="on")){var y=document.getElementById("slimstat-gdpr-banner");if(y){g=!0,setTimeout(function(){y&&y.classList?y.classList.add("show"):y&&(y.style.display="block")},50);for(var I=y.querySelectorAll("[data-consent]"),H=0;H<I.length;H++)(function(M){M.addEventListener?M.addEventListener("click",function(A){A&&typeof A.preventDefault=="function"&&A.preventDefault(),A&&typeof A.stopPropagation=="function"&&A.stopPropagation();var X=M.getAttribute("data-consent")||"";C(X,y)},!1):M.attachEvent?M.attachEvent("onclick",function(A){A&&typeof A.preventDefault=="function"&&A.preventDefault(),A&&typeof A.stopPropagation=="function"&&A.stopPropagation();var X=M.getAttribute("data-consent")||"";C(X,y)}):M.onclick=function(A){A&&typeof A.preventDefault=="function"&&A.preventDefault(),A&&typeof A.stopPropagation=="function"&&A.stopPropagation();var X=M.getAttribute("data-consent")||"";C(X,y)}})(I[H])}}}}function C(b,y){if(!(!b||b!=="accepted"&&b!=="denied")){var I=f(),H=I.wp_rest_nonce||"",M=I.gdpr_cookie_name||"slimstat_gdpr_consent",A=I.gdpr_cookie_path||I.baseurl||"/";try{var X=new Date;X.setTime(X.getTime()+365*24*60*60*1e3);var ue=M+"="+b+"; path="+A+"; expires="+X.toUTCString()+"; SameSite=Lax";window&&window.location&&window.location.protocol==="https:"&&(ue+="; Secure"),document.cookie=ue}catch{}if(y&&y.classList?(y.classList.remove("show"),y.classList.add("hiding")):y&&(y.style.transition="transform 0.3s ease-out, opacity 0.3s ease-out",y.style.transform="translateY(100%)",y.style.opacity="0"),setTimeout(function(){y&&y.parentNode&&y.parentNode.removeChild(y)},350),b==="accepted"){try{if(typeof CustomEvent=="function")document.dispatchEvent(new CustomEvent("slimstat_banner_consent",{detail:{consent:b}}));else{var ae=document.createEvent("Event");ae.initEvent("slimstat_banner_consent",!0,!0),document.dispatchEvent(ae)}}catch{}try{var te=normalizeConsent(b),ge=null;I.id&&parseInt(I.id,10)>0&&(ge=parseInt(I.id,10)),sendConsentChangeToServer("slimstat_banner",te,ge)}catch{}try{p({consent:b,consentNonce:H})}catch{}}else if(b==="denied"){try{var ye=normalizeConsent(b);sendConsentChangeToServer("slimstat_banner",ye,null)}catch{}try{var fe=I.ajaxurl||"/wp-admin/admin-ajax.php",me=new XMLHttpRequest;me.open("POST",fe,!0),me.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),me.send("action=slimstat_consent_revoked&nonce="+encodeURIComponent(H)),me.onload=function(){},me.onerror=function(){}}catch{}}}}document.readyState&&document.readyState!=="loading"&&h(),document.addEventListener?(document.addEventListener("DOMContentLoaded",h,!1),window.addEventListener("load",h,!1)):document.attachEvent?(document.attachEvent("onreadystatechange",function(){document.readyState==="complete"&&h()}),window.attachEvent("onload",h)):(document.readyState==="complete"&&h(),window.onload=h)}Se(),Le()})();})(); +//# sourceMappingURL=wp-slimstat.min.js.map Only in /home/deploy/wp-safety.org/data/plugin-versions/wp-slimstat/5.4.0: wp-slimstat.min.js.map @@ -3,7 +3,7 @@ * Plugin Name: SlimStat Analytics * Plugin URI: https://wp-slimstat.com/ * Description: The leading web analytics plugin for WordPress - * Version: 5.3.5 + * Version: 5.4.0 * Author: Jason Crouse, VeronaLabs * Text Domain: wp-slimstat * Domain Path: /languages @@ -14,17 +14,13 @@ * Requires PHP: 7.4 */ -if (!empty(wp_slimstat::$settings)) { - return true; -} - // check if composer autoloader exists if (!file_exists(__DIR__ . '/vendor/autoload.php')) { return; } // Set the plugin version and directory -define('SLIMSTAT_ANALYTICS_VERSION', '5.3.5'); +define('SLIMSTAT_ANALYTICS_VERSION', '5.4.0'); define('SLIMSTAT_FILE', __FILE__); define('SLIMSTAT_DIR', __DIR__); define('SLIMSTAT_URL', plugins_url('', __FILE__)); @@ -32,6 +28,29 @@ // include the autoloader if it exists require_once __DIR__ . '/vendor/autoload.php'; +// Include Constants.php to make SLIMSTAT_ANALYTICS_DIR available to traits +require_once __DIR__ . '/src/Constants.php'; + + +/** + * Main Slimstat Analytics Class + * + * @package Wp_SlimStat + * + * @todo REFACTOR TRACKING STATE: The $data_js and $stat properties should be refactored into a + * proper state object pattern to maintain encapsulation. Currently these properties are + * public to support refactored tracker classes (SlimStat\Tracker\*), but this breaks + * encapsulation and creates security risks. Future implementation should: + * 1. Create a TrackingState class to encapsulate state management + * 2. Update all Tracker classes to use the state object + * 3. Make properties protected or private + * 4. Ensure all state modifications go through validated methods + * This is tracked as technical debt for version 6.0 + */ + +// Include Constants.php to make SLIMSTAT_ANALYTICS_DIR available to traits +require_once __DIR__ . '/src/Constants.php'; + class wp_slimstat { public static $settings = []; @@ -42,16 +61,102 @@ public static $update_checker = []; public static $raw_post_array = []; + /** + * @var array Tracking data from JavaScript (for internal tracking use only) + * @internal Use get_data_js() / set_data_js() methods for controlled access. + * + * This property is now protected to maintain proper encapsulation and prevent external code + * from bypassing consent checks or corrupting tracking state. All tracker classes use the + * getter/setter methods which include validation and filter hooks for GDPR compliance. + */ protected static $data_js = ['id' => 0]; + + /** + * @var array Current pageview tracking data (for internal tracking use only) + * @internal Use get_stat() / set_stat() methods for controlled access. + * + * This property is now protected to maintain proper encapsulation and prevent external code + * from bypassing consent checks or corrupting tracking state. All tracker classes use the + * getter/setter methods which include validation and filter hooks for GDPR compliance. + */ protected static $stat = []; + protected static $date_i18n_filters = []; /** + * Gets the current data_js array (for internal tracking use only) + * + * @return array + */ + public static function get_data_js() + { + return self::$data_js; + } + + /** + * Sets the data_js array (for internal tracking use only) + * + * This method provides controlled access to the data_js property and includes + * basic validation to prevent tampering. + * + * @param array $data_js The tracking data from JavaScript + * @return void + * @internal For use by SlimStat tracking classes only + */ + public static function set_data_js($data_js) + { + // Validate that we're receiving an array + if (!is_array($data_js)) { + return; + } + + // Apply filter to allow validation/modification by consent management systems + $data_js = apply_filters('slimstat_set_data_js', $data_js); + + self::$data_js = $data_js; + } + + /** + * Gets the current stat array (for internal tracking use only) + * + * @return array Current tracking state + * @internal For use by SlimStat tracking classes only + */ + public static function get_stat() + { + return self::$stat; + } + + /** + * Sets the stat array (for internal tracking use only) + * + * This method provides controlled access to the stat property and includes + * basic validation to prevent tampering and ensure consent compliance. + * + * @param array $stat The pageview tracking data + * @return void + * @internal For use by SlimStat tracking classes only + */ + public static function set_stat($stat) + { + // Validate that we're receiving an array + if (!is_array($stat)) { + return; + } + + // Apply filter to allow validation/modification by consent management systems + // This is critical for GDPR compliance - CMPs can inspect and modify data + $stat = apply_filters('slimstat_set_stat', $stat); + + self::$stat = $stat; + } + + /** * Initializes variables and actions */ public static function init() { - \SlimStat\Providers\RESTService::run(); + \SlimStat\Providers\RestApiManager::run(); // Load all the settings if (is_network_admin() && (empty($_GET['page']) || false === strpos($_GET['page'], 'slimview'))) { @@ -68,7 +173,26 @@ self::$settings = array_merge(self::init_options(), self::$settings); // Allow third party tools to edit the options - self::$settings = apply_filters('slimstat_init_options', self::$settings); + self::$settings = apply_filters('slimstat_init_options', self::$settings); + + $consent_integration = self::$settings['consent_integration'] ?? ''; + + // If WP Consent API is selected but the function doesn't exist, reset to default + if ('wp_consent_api' === $consent_integration && !function_exists('wp_has_consent')) { + $consent_integration = ''; + self::$settings['consent_integration'] = ''; + } + + if ('' === $consent_integration && ('on' === (self::$settings['use_slimstat_banner'] ?? 'off'))) { + $consent_integration = 'slimstat_banner'; + self::$settings['consent_integration'] = $consent_integration; + } + + if ('slimstat_banner' === $consent_integration) { + self::$settings['use_slimstat_banner'] = 'on'; + } else { + self::$settings['use_slimstat_banner'] = 'off'; + } // Allow third-party tools to use a custom database for Slimstat self::$wpdb = apply_filters('slimstat_custom_wpdb', $GLOBALS['wpdb']); @@ -100,10 +224,10 @@ // Is server-side tracking active? if ('on' != self::$settings['javascript_mode']) { - add_action(is_admin() ? 'admin_init' : 'wp', [self::class, 'slimtrack'], 5); + add_action(is_admin() ? 'admin_init' : 'wp', [\SlimStat\Tracker\Tracker::class, 'slimtrack'], 5); if ('on' != self::$settings['ignore_wp_users']) { - add_action('login_init', [self::class, 'slimtrack'], 10); + add_action('login_init', [\SlimStat\Tracker\Tracker::class, 'slimtrack'], 10); } } @@ -113,24 +237,66 @@ add_action('login_enqueue_scripts', [self::class, 'enqueue_tracker'], 10); } - add_filter('script_loader_tag', [self::class, 'add_defer_to_script_tag'], 10, 2); + add_filter('script_loader_tag', [self::class, 'add_defer_to_script_tag'], 10, 2); + } + + $banner_enabled = ('on' === (self::$settings['use_slimstat_banner'] ?? 'off')); + if ($banner_enabled) { + add_action('wp_enqueue_scripts', [self::class, 'enqueue_gdpr_assets'], 20); + add_action('login_enqueue_scripts', [self::class, 'enqueue_gdpr_assets'], 20); + add_action('wp_footer', [self::class, 'render_gdpr_banner'], 5); + add_action('login_footer', [self::class, 'render_gdpr_banner'], 5); + } + + // Registers Slimstat with WP Consent API if enabled in plugin settings + if ((self::$settings['consent_integration'] ?? '') === 'wp_consent_api') { + // Check if WP Consent API plugin is actually active + if (function_exists('wp_has_consent')) { + $plugin = plugin_basename(SLIMSTAT_FILE); + add_filter("wp_consent_api_registered_{$plugin}", '__return_true'); + + // Register cookie info with WP Consent API for CMP display + if (function_exists('wp_add_cookie_info')) { + wp_add_cookie_info( + 'slimstat_tracking_code', + 'SlimStat Analytics', + 'statistics', + intval(self::$settings['session_duration'] ?? 1800) . ' ' . __('seconds', 'wp-slimstat'), + __('Session cookie that identifies returning visitors for analytics.', 'wp-slimstat'), + '', + false, + false + ); + } + } } + // Register WordPress Privacy API exporters and erasers (GDPR Article 15 & 17) + add_filter('wp_privacy_personal_data_exporters', [\SlimStat\Services\Privacy\DataExporter::class, 'registerExporters']); + add_filter('wp_privacy_personal_data_erasers', [\SlimStat\Services\Privacy\DataEraser::class, 'registerErasers']); + + // Register privacy policy content + add_action('admin_init', [self::class, 'registerPrivacyPolicyContent']); + + // Register AJAX handlers for consent upgrade/revocation (anonymous tracking mode) + \SlimStat\Services\Privacy\ConsentHandler::registerAjaxHandlers(); + // Hook a DB clean-up routine to the daily cronjob add_action('wp_slimstat_purge', [self::class, 'wp_slimstat_purge']); + // Hook IP hashing daily salt generation (for GDPR compliance) + add_action('wp_slimstat_generate_daily_salt', [\SlimStat\Providers\IPHashProvider::class, 'generateDailySalt']); + // Hook a GeoIP database update routine to the daily cronjob add_action('wp_slimstat_update_geoip_database', [self::class, 'wp_slimstat_update_geoip_database']); // Allow external domains on CORS requests add_filter('allowed_http_origins', [self::class, 'open_cors_admin_ajax']); - // GDPR: Opt-out Ajax Handler - add_action('wp_ajax_slimstat_optout_html', [self::class, 'get_optout_html']); - add_action('wp_ajax_nopriv_slimstat_optout_html', [self::class, 'get_optout_html']); + // Internal GDPR banner/consent handling removed. Use external CMP plugins. // If this request was a redirect, we should update the content type accordingly - add_filter('wp_redirect_status', [self::class, 'update_content_type'], 10, 2); + add_filter('wp_redirect_status', [\SlimStat\Tracker\Tracker::class, 'update_content_type'], 10, 2); // Shortcodes add_shortcode('slimstat', [self::class, 'slimstat_shortcode'], 15); @@ -141,13 +307,8 @@ // REST API Support add_action('rest_api_init', [self::class, 'register_rest_route']); - // Rewrite rule for static tracker - add_action('init', [self::class, 'rewrite_rule_tracker']); - add_action('template_redirect', [self::class, 'adblocker_javascript']); - // Load the admin library if (is_user_logged_in()) { - include_once(plugin_dir_path(__FILE__) . 'src/Constants.php'); include_once(plugin_dir_path(__FILE__) . 'admin/index.php'); add_action('init', ['wp_slimstat_admin', 'init'], 60); } @@ -155,199 +316,14 @@ // end init /** - * Reads and processes the data received by the XHR tracker + * Load plugin textdomain + * + * @return void */ - public static function slimtrack_ajax() + public static function load_textdomain() { - // If the website is using a caching plugin, the tracking code might still be there, even if the user turned off tracking - if ('on' != self::$settings['is_tracking']) { - exit(self::_log_error(204)); - } - - $id = 0; - - self::$data_js = apply_filters('slimstat_filter_pageview_data_js', self::$raw_post_array); - $site_host = parse_url(get_site_url(), PHP_URL_HOST); - - self::$stat['referer'] = ''; - if (!empty(self::$data_js['ref'])) { - self::$stat['referer'] = self::_base64_url_decode(self::$data_js['ref']); - - $parsed_ref = parse_url(self::$stat['referer'], PHP_URL_HOST); - if (false === $parsed_ref) { - exit(self::_log_error(201)); - } - } - - // Do we have an id for this request? If we do, we are either updating an existing pageview, or recording an event on the page - if (!empty(self::$data_js['id'])) { - - // Make sure that the control code is valid - self::$data_js['id'] = self::_get_value_without_checksum(self::$data_js['id']); - - if (false === self::$data_js['id']) { - exit(self::_log_error(101)); - } - - self::$stat['id'] = intval(self::$data_js['id']); - if (self::$stat['id'] < 0) { - do_action('slimstat_track_exit_' . abs(self::$stat['id'])); - exit(self::_get_value_with_checksum(self::$stat['id'])); - } - - // If self::$data_js[ 'pos' ] is empty, update an existing pageview with client-based information (resolution, server latency, etc) - if (empty(self::$data_js['pos'])) { - self::_set_visit_id(true); - - // Retrieves all the client-side info (screen resolution, server latency, etc) and sets the corresponding entries in self::$stat - self::$stat = self::_get_client_info(self::$data_js, self::$stat); - - // Visitor is still on this page, record the timestamp in the corresponding field if this WAS NOT a request to update a "server-side" pageview with client-side info - if (empty(self::$stat['resolution'])) { - // Heartbeat / finalize update of dt_out - if (!empty(self::$data_js['hb'])) { - // Use provided ts if valid, else current time - $heartbeat_ts = 0; - if (!empty(self::$data_js['ts'])) { - $heartbeat_ts = intval(self::$data_js['ts']); - } - if ($heartbeat_ts > 0 && $heartbeat_ts <= (time() + 300)) { // sanity: not too far future - self::$stat['dt_out'] = $heartbeat_ts; - } else { - self::$stat['dt_out'] = self::date_i18n('U'); - } - } else { - self::$stat['dt_out'] = self::date_i18n('U'); - } - } - - // Is this a new visitor, based on his fingerprint? - if (!empty(self::$stat['fingerprint']) && self::_is_new_visitor(self::$stat['fingerprint'])) { - self::$stat['notes'] = ['new:yes']; - } - - $id = self::_update_row(self::$stat); - } // ... otherwise, is this an event: a click on a link (maybe a 'download'?) or other user action - else { - // Record the event - $event_info = [ - 'position' => strip_tags(trim(self::$data_js['pos'])), - 'id' => self::$stat['id'], - 'dt' => self::date_i18n('U'), - ]; - - if (!empty(self::$data_js['no'])) { - $event_info['notes'] = self::_base64_url_decode(self::$data_js['no']); - } - - /** - * Allow third-party tools to decide whether to track this event or not - * - * @param bool $shouldEventBeTracked - * @param array $event_info - * - * @return bool - * - * @since 5.2.6 - */ - $shouldEventBeTracked = apply_filters('slimstat_track_event_enabled', true, $event_info); - - if ($shouldEventBeTracked) { - self::_insert_row($event_info, $GLOBALS['wpdb']->prefix . 'slim_events'); - } - - if (!empty(self::$data_js['res'])) { - $resource = self::_base64_url_decode(self::$data_js['res']); - $parsed_resource = parse_url($resource); - - if (false === $parsed_resource || empty($parsed_resource['host'])) { - exit(self::_log_error(203)); - } - - // Is this a download? If it is, add a new record to the database - if (!empty($parsed_resource['path']) && in_array(pathinfo($parsed_resource['path'], PATHINFO_EXTENSION), self::string_to_array(self::$settings['extensions_to_track']))) { - self::$stat['resource'] = $parsed_resource['path'] . (empty($parsed_resource['query']) ? '' : '?' . $parsed_resource['query']); - self::$stat['content_type'] = 'download'; - - if (!empty(self::$data_js['fh'])) { - self::$stat['fingerprint'] = sanitize_text_field(self::$data_js['fh']); - } - - $id = self::slimtrack(); - } // .. or outbound link? If so, update the pageview with the new info - elseif ($parsed_resource['host'] != $site_host) { - self::$stat['outbound_resource'] = sanitize_url($resource); - - // Visitor is still on this page, record the timestamp in the corresponding field - self::$stat['dt_out'] = self::date_i18n('U'); - - $id = self::_update_row(self::$stat); - } - } else { - // Visitor is still on this page, record the timestamp in the corresponding field - self::$stat['dt_out'] = self::date_i18n('U'); - - $id = self::_update_row(self::$stat); - } - } - } // If self::$data_js[ 'id' ] is empty, we are tracking a new pageview - else { - self::$stat['resource'] = ''; - if (!empty(self::$data_js['res'])) { - self::$stat['resource'] = self::_base64_url_decode(self::$data_js['res']); - - if (false === parse_url(self::$stat['resource'])) { - exit(self::_log_error(203)); - } - } - - // Retrieves all the client-side info (screen resolution, server latency, etc) and sets the corresponding entries in self::$stat - self::$stat = self::_get_client_info(self::$data_js, self::$stat); - - if (!empty(self::$data_js['ci'])) { - self::$data_js['ci'] = self::_get_value_without_checksum(self::$data_js['ci']); - - if (false === self::$data_js['ci']) { - exit(self::_log_error(102)); - } - - $content_info = @unserialize(self::_base64_url_decode(self::$data_js['ci'])); - - if (empty($content_info) || !is_array($content_info)) { - exit(self::_log_error(103)); - } - - foreach (['content_type', 'category', 'content_id', 'author'] as $a_key) { - if (!empty($content_info[$a_key]) && 'content_id' !== $a_key) { - self::$stat[$a_key] = sanitize_text_field($content_info[$a_key]); - } elseif (!empty($content_info[$a_key])) { - self::$stat[$a_key] = absint($content_info[$a_key]); - } - } - } // ... otherwise we'll track this as an external page - else { - self::$stat['content_type'] = 'external'; - } - - // Is this a new visitor, based on his fingerprint? - if (!empty(self::$stat['fingerprint']) && self::_is_new_visitor(self::$stat['fingerprint'])) { - self::$stat['notes'] = ['new:yes']; - } - - // Track the rest of the information related to this pageview - $id = self::slimtrack(); - } - - // Was this pageview tracked? - if (empty($id)) { - exit(0); - } - - // Send the ID back to Javascript to track future interactions - do_action('slimstat_track_success'); - exit(self::_get_value_with_checksum($id)); + load_plugin_textdomain('wp-slimstat', false, '/wp-slimstat/languages'); } - // end slimtrack_ajax /** * The main logging function @@ -372,442 +348,6 @@ } /** - * Rewrite rule for static tracker - */ - public static function rewrite_rule_tracker() - { - if ('adblock_bypass' === (self::$settings['tracking_request_method'] ?? 'rest')) { - add_rewrite_tag('%slimstat_tracker%', '([a-f0-9]{32})'); - add_rewrite_rule( - '^([a-f0-9]{32})\\.js$', - 'index.php?slimstat_tracker=$matches[1]', - 'top' - ); - } - } - - /** - * Function to detect if Adblock is enabled and serve the JS tracker - */ - public static function adblocker_javascript() - { - // Only handle the tracker JS endpoint if adblock bypass is enabled - if ('adblock_bypass' !== (self::$settings['tracking_request_method'] ?? 'rest')) { - return; - } - - $tracker_hash = get_query_var('slimstat_tracker'); - if ($tracker_hash && $tracker_hash === md5(site_url() . 'slimstat')) { - // Set the content type to JavaScript - header('Content-Type: application/javascript'); - - // Set caching headers for one year - header('Cache-Control: public, max-age=31536000'); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT'); - - $js_path = plugin_dir_path(__FILE__) . '/wp-slimstat.min.js'; - - if (file_exists($js_path)) { - readfile($js_path); - exit; - } else { - status_header(404); - echo '// Tracker not found'; - exit; - } - } - } - - /** - * THE Slimstat tracker - */ - public static function slimtrack() - { - self::$stat['dt'] = self::date_i18n('U'); - - if (empty(self::$stat['notes'])) { - self::$stat['notes'] = []; - } - - // Allow third-party tools to initialize the stat array - self::$stat = apply_filters('slimstat_filter_pageview_stat_init', self::$stat); - - // Third-party tools can decide that this pageview should not be tracked, by setting its datestamp to zero - if (empty(self::$stat) || empty(self::$stat['dt'])) { - return false; - } - - // Reset the pageview ID, if it's set for some obscure reason - unset(self::$stat['id']); - - // Opt-out of tracking via cookie - if ('on' == self::$settings['display_opt_out']) { - $cookie_names = ['slimstat_optout_tracking' => 'true']; - - if (!empty(self::$settings['opt_out_cookie_names'])) { - $cookie_names = []; - - foreach (self::string_to_array(self::$settings['opt_out_cookie_names']) as $a_cookie_pair) { - [$name, $value] = explode('=', $a_cookie_pair); - - if ('' !== $name && '0' !== $name && ('' !== $value && '0' !== $value)) { - $cookie_names[$name] = $value; - } - } - } - - foreach ($cookie_names as $a_name => $a_value) { - if (isset($_COOKIE[$a_name]) && false !== strpos($_COOKIE[$a_name], $a_value)) { - // remove slimstat cookie - unset($_COOKIE['slimstat_tracking_code']); - @setcookie( - 'slimstat_tracking_code', - '', - ['expires' => time() - (15 * 60), 'path' => COOKIEPATH] - ); - return false; - } - } - } - - // Opt-in tracking via cookie (only those who have a cookie will be tracked) - if (!empty(self::$settings['opt_in_cookie_names'])) { - $cookie_names = []; - $opt_in_cookie_names = self::string_to_array(self::$settings['opt_in_cookie_names']); - - foreach ($opt_in_cookie_names as $a_cookie_pair) { - [$name, $value] = explode('=', $a_cookie_pair); - - if ('' !== $name && '0' !== $name && ('' !== $value && '0' !== $value)) { - $cookie_names[$name] = $value; - } - } - - $cookie_found = false; - foreach ($cookie_names as $a_name => $a_value) { - if (isset($_COOKIE[$a_name]) && false !== strpos($_COOKIE[$a_name], $a_value)) { - $cookie_found = true; - } - } - - if (!$cookie_found) { - // remove slimstat cookie - unset($_COOKIE['slimstat_tracking_code']); - @setcookie( - 'slimstat_tracking_code', - '', - ['expires' => time() - (15 * 60), 'path' => COOKIEPATH] - ); - return false; - } - } - - // IP address - [self::$stat['ip'], self::$stat['other_ip']] = self::_get_remote_ip(); - - if (empty(self::$stat['ip']) || '0.0.0.0' == self::$stat['ip']) { - $error = self::_log_error(202); - return false; - } - - // Should we ignore this IP address? - foreach (self::string_to_array(self::$settings['ignore_ip']) as $a_ip_range) { - $ip_to_ignore = $a_ip_range; - - if (false !== strpos($ip_to_ignore, '/')) { - [$ip_to_ignore, $cidr_mask] = explode('/', trim($ip_to_ignore)); - } else { - $cidr_mask = self::_get_mask_length($ip_to_ignore); - } - - $long_masked_ip_to_ignore = substr(self::_dtr_pton($ip_to_ignore), 0, $cidr_mask); - $long_masked_user_ip = substr(self::_dtr_pton(self::$stat['ip']), 0, $cidr_mask); - $long_masked_user_other_ip = substr(self::_dtr_pton(self::$stat['other_ip']), 0, $cidr_mask); - - if ($long_masked_user_ip === $long_masked_ip_to_ignore || $long_masked_user_other_ip === $long_masked_ip_to_ignore) { - return false; - } - } - - // Do we need to anonymize this IP address? - if ('on' == self::$settings['anonymize_ip']) { - self::$stat['ip'] = wp_privacy_anonymize_ip(self::$stat['ip']); - - if (!empty(self::$stat['other_ip'])) { - self::$stat['other_ip'] = wp_privacy_anonymize_ip(self::$stat['other_ip']); - } - } - - // Resource URL - if (!isset(self::$stat['resource'])) { - self::$stat['resource'] = self::get_request_uri(); - } - - // Decode the URL and sanitize it to ensure it's safe and properly formatted - self::$stat['resource'] = sanitize_text_field(urldecode(self::$stat['resource'])); - - // Re-encode non-ASCII chars, preserving ASCII and slashes for backwards compatibility - self::$stat['resource'] = preg_replace_callback('/[^\x20-\x7E]/', fn ($match) => '%' . bin2hex($match[0]), self::$stat['resource']); - - // Is this a 'seriously malformed' URL? - $parsed_url = parse_url(self::$stat['resource']); - if (!$parsed_url) { - $error = self::_log_error(203); - return false; - } - - // Don't store the domain name in the database - self::$stat['resource'] = $parsed_url['path'] . (empty($parsed_url['query']) ? '' : '?' . $parsed_url['query']) . (empty($parsed_url['fragment']) ? '' : '#' . $parsed_url['fragment']); - - // Is this resource blacklisted? - if (!empty(self::$settings['ignore_resources']) && self::_is_blacklisted(self::$stat['resource'], self::$settings['ignore_resources'])) { - return false; - } - - // Referrer URL - if (empty(self::$stat['referer']) && !empty($_SERVER['HTTP_REFERER'])) { - self::$stat['referer'] = sanitize_url(wp_unslash($_SERVER['HTTP_REFERER'])); - } - - if (!empty(self::$stat['referer'])) { - // Is this a 'seriously malformed' URL? - $parsed_url = parse_url(self::$stat['referer']); - if (!$parsed_url) { - $error = self::_log_error(201); - return false; - } - - if (isset($parsed_url['scheme']) && ('' !== $parsed_url['scheme'] && '0' !== $parsed_url['scheme']) && !in_array(strtolower($parsed_url['scheme']), ['http', 'https', 'android-app'])) { - self::$stat['notes'][] = sprintf(__('Attempted XSS Injection: %s', 'wp-slimstat'), self::$stat['referer']); - unset(self::$stat['referer']); - } - - // Is this referer blacklisted? - if (!empty(self::$settings['ignore_referers']) && self::_is_blacklisted(self::$stat['referer'], self::$settings['ignore_referers'])) { - return false; - } - - // Search terms - self::$stat['searchterms'] = self::_get_search_terms(self::$stat['referer']); - - // Are we storing internal referrers in the database? - $parsed_site_url = parse_url(get_site_url(), PHP_URL_HOST); - if (isset($parsed_url['host']) && ('' !== $parsed_url['host'] && '0' !== $parsed_url['host']) && $parsed_url['host'] == $parsed_site_url && 'on' != self::$settings['track_same_domain_referers']) { - unset(self::$stat['referer']); - } - } - - // Internal WP search? - if (empty(self::$stat['searchterms']) && !empty($_POST['s'])) { - self::$stat['searchterms'] = sanitize_text_field(str_replace('\\', '', $_REQUEST['s'])); - } - - // If this function was called by the js tracker (client mode), we've already determined this pageview's content information - if (!isset(self::$stat['content_type'])) { - $content_info = self::_get_content_info(); - - // Is this content type blacklisted? - if (!empty(self::$settings['ignore_content_types']) && self::_is_blacklisted($content_info['content_type'], self::$settings['ignore_content_types'])) { - return false; - } - - if (is_array($content_info)) { - self::$stat += $content_info; - } - } - - // Number of results from query_posts - if ((is_archive() || is_search()) && !empty($GLOBALS['wp_query']->found_posts)) { - self::$stat['notes'][] = 'results:' . intval($GLOBALS['wp_query']->found_posts); - } - - // Do not track report pages in the admin - if ((!empty(self::$stat['resource']) && false !== strpos(self::$stat['resource'], 'wp-admin/admin-ajax.php')) || (!empty($_GET['page']) && false !== strpos($_GET['page'], 'slimview'))) { - return false; - } - - // Should we ignore this user? - if (!empty($GLOBALS['current_user']->ID)) { - // Don't track logged-in users, if the corresponding option is enabled - if ('on' == self::$settings['ignore_wp_users']) { - return false; - } - - // Don't track users with given capabilities - foreach ($GLOBALS['current_user']->roles as $a_capability) { - if (self::_is_blacklisted($a_capability, self::$settings['ignore_capabilities'])) { - return false; - } - } - - // Is this user blacklisted? - if (!empty(self::$settings['ignore_users']) && self::_is_blacklisted($GLOBALS['current_user']->data->user_login, self::$settings['ignore_users'])) { - return false; - } - - self::$stat['username'] = $GLOBALS['current_user']->data->user_login; - self::$stat['email'] = $GLOBALS['current_user']->data->user_email; - self::$stat['notes'][] = 'user:' . $GLOBALS['current_user']->data->ID; - $not_spam = true; - } elseif (isset($_COOKIE['comment_author_' . COOKIEHASH])) { - // Is this a spammer? - $spam_comment = self::$wpdb->get_row(self::$wpdb->prepare(' - SELECT comment_author, comment_author_email, COUNT(*) comment_count - FROM `' . DB_NAME . "`.{$GLOBALS['wpdb']->comments} - WHERE comment_author_IP = %s AND comment_approved = 'spam' - GROUP BY comment_author - LIMIT 0,1", self::$stat['ip']), ARRAY_A); - - if (!empty($spam_comment['comment_count'])) { - if ('on' == self::$settings['ignore_spammers']) { - return false; - } else { - self::$stat['notes'][] = 'spam:yes'; - self::$stat['username'] = $spam_comment['comment_author']; - self::$stat['email'] = $spam_comment['comment_author_email']; - } - } else { - if (!empty($_COOKIE['comment_author_' . COOKIEHASH])) { - self::$stat['username'] = sanitize_user($_COOKIE['comment_author_' . COOKIEHASH]); - } - if (!empty($_COOKIE['comment_author_email_' . COOKIEHASH])) { - self::$stat['email'] = sanitize_email($_COOKIE['comment_author_email_' . COOKIEHASH]); - } - } - } - - // Language - self::$stat['language'] = self::_get_language(); - - // Is this language blacklisted? - if (!empty(self::$stat['language']) && !empty(self::$settings['ignore_languages']) && false !== stripos(self::$settings['ignore_languages'], (string) self::$stat['language'])) { - return false; - } - - // Geolocation - $geographicProvider = new \SlimStat\Services\GeoService(); - if ($geographicProvider->isGeoIPEnabled()) { - try { - $geolocation_data = \SlimStat\Services\GeoIP::loader(self::$stat['ip']); - } catch (Exception $e) { - self::_log_error(205); - return false; - } - - if (!empty($geolocation_data['country']['iso_code']) && 'xx' != $geolocation_data['country']['iso_code']) { - self::$stat['country'] = strtolower($geolocation_data['country']['iso_code']); - - if (!empty($geolocation_data['city']['names']['en'])) { - self::$stat['city'] = $geolocation_data['city']['names']['en']; - } - - if (!empty($geolocation_data['subdivisions'][0]['iso_code']) && !empty(self::$stat['city'])) { - self::$stat['city'] .= ' (' . $geolocation_data['subdivisions'][0]['iso_code'] . ')'; - } - - if (!empty($geolocation_data['location']['latitude']) && !empty($geolocation_data['location']['longitude'])) { - self::$stat['location'] = $geolocation_data['location']['latitude'] . ',' . $geolocation_data['location']['longitude']; - } - } - - // Is this country blacklisted? - if (!empty(self::$stat['country']) && !empty(self::$settings['ignore_countries']) && false !== stripos(self::$settings['ignore_countries'], (string) self::$stat['country'])) { - return false; - } - } - - // Mark or ignore Firefox/Safari prefetching requests (X-Moz: Prefetch and X-purpose: Preview) - if ((isset($_SERVER['HTTP_X_MOZ']) && ('prefetch' === strtolower($_SERVER['HTTP_X_MOZ']))) || (isset($_SERVER['HTTP_X_PURPOSE']) && ('preview' === strtolower($_SERVER['HTTP_X_PURPOSE'])))) { - if ('on' == self::$settings['ignore_prefetch']) { - return false; - } else { - self::$stat['notes'][] = 'pre:yes'; - } - } - - // User Agent - $browser = \SlimStat\Services\Browscap::get_browser(); - - // Are we ignoring bots? - if ('on' == self::$settings['ignore_bots'] && 1 == $browser['browser_type']) { - return false; - } - - // Is this browser blacklisted? - if (!empty(self::$settings['ignore_browsers']) && self::_is_blacklisted([$browser['browser'], $browser['user_agent']], self::$settings['ignore_browsers'])) { - return false; - } - - // Is this operating system blacklisted? - if (!empty(self::$settings['ignore_platforms']) && self::_is_blacklisted($browser['platform'], self::$settings['ignore_platforms'])) { - return false; - } - - self::$stat += $browser; - - // Do we need to assign a visit_id to this user? - $cookie_has_been_set = self::_set_visit_id(false); - - // Allow third-party tools to modify all the data we've gathered so far - self::$stat = apply_filters('slimstat_filter_pageview_stat', self::$stat); - do_action('slimstat_track_pageview', self::$stat); - - // Third-party tools can decide that this pageview should not be tracked, by setting its datestamp to zero - if (empty(self::$stat) || empty(self::$stat['dt'])) { - return false; - } - - // Implode the notes - if (!empty(self::$stat['notes'])) { - self::$stat['notes'] = '[' . implode('][', self::$stat['notes']) . ']'; - } - - // Remove empty values - self::$stat = array_filter(self::$stat); - - // Save this information in the database - self::$stat['id'] = self::_insert_row(self::$stat, $GLOBALS['wpdb']->prefix . 'slim_stats'); - - // Did something go wrong during the insert? - if (empty(self::$stat['id'])) { - - // Attempt to init the environment (plugin just activated on a blog in a MU network?) - include_once(plugin_dir_path(__FILE__) . 'admin/index.php'); - wp_slimstat_admin::init_environment(); - - // Now let's try again - self::$stat['id'] = self::_insert_row(self::$stat, $GLOBALS['wpdb']->prefix . 'slim_stats'); - - if (empty(self::$stat['id'])) { - $error = self::_log_error(200); - return false; - } - } - - // Does this visitor have a visit_id cookie? - $set_cookie = apply_filters('slimstat_set_visit_cookie', (!empty(self::$settings['set_tracker_cookie']) && 'on' == self::$settings['set_tracker_cookie'])); - if ($set_cookie) { - if (empty(self::$stat['visit_id']) && !empty(self::$stat['id'])) { - // Set a cookie to track this visit (Google and other non-human engines will just ignore it) - @setcookie( - 'slimstat_tracking_code', - self::_get_value_with_checksum(self::$stat['id'] . 'id'), - ['expires' => time() + 2678400, 'path' => COOKIEPATH] - ); - } elseif (!$cookie_has_been_set && 'on' == self::$settings['extend_session'] && self::$stat['visit_id'] > 0) { - @setcookie( - 'slimstat_tracking_code', - self::_get_value_with_checksum(self::$stat['visit_id']), - ['expires' => time() + self::$settings['session_duration'], 'path' => COOKIEPATH] - ); - } - } - - return self::$stat['id']; - } - // end slimtrack - - /** * Decodes the permalink */ public static function get_request_uri() @@ -857,9 +397,6 @@ $as_column = ''; $s = sprintf("<span class='slimstat-item-separator'>%s</span>", $s); - // Load the localization files (for languages, operating systems, etc) - load_plugin_textdomain('wp-slimstat', false, '/wp-slimstat/languages'); - // Look for required fields if (empty($f) || empty($w)) { return '<!-- Slimstat Shortcode Error: missing parameter -->'; @@ -1026,6 +563,7 @@ // end slimstat_shortcode + public static function init_plugin() { // Include our browser detector library @@ -1033,6 +571,15 @@ // Make sure the upload directory is exist and is protected. self::create_upload_directory(); + + // Ensure daily salt exists for IP hashing (GDPR compliance) + // This runs on every page load but only generates if missing + \SlimStat\Providers\IPHashProvider::generateDailySalt(); + + // Initialize adblock bypass functionality + \SlimStat\Tracker\Tracker::rewrite_rule_tracker(); + add_action('template_redirect', [\SlimStat\Tracker\Tracker::class, 'adblocker_javascript']); + add_action('init', [\SlimStat\Tracker\Tracker::class, 'rewrite_rule_tracker']); } /** @@ -1112,7 +659,13 @@ if (empty($_request['token'])) { return new WP_Error('rest_invalid', esc_html__('[REST API] Please use a valid token in order to access the REST API endpoint at this URL.', 'wp-slimstat'), ['status' => 400]); } - return in_array($_request['token'], self::string_to_array(self::$settings['rest_api_tokens'])); + $valid_tokens = self::string_to_array(self::$settings['rest_api_tokens']); + foreach ($valid_tokens as $valid_token) { + if (is_string($valid_token) && is_string($_request['token']) && hash_equals($valid_token, $_request['token'])) { + return true; + } + } + return false; } // end rest_api_authorization @@ -1163,6 +716,33 @@ // end string_to_array /** + * Returns Matomo search engine mapping JSON, cached. + */ + public static function get_search_engines() + { + static $cached_search_engines = null; + if (null !== $cached_search_engines) { + return $cached_search_engines; + } + + $data = get_transient('slimstat_matomo_searchengine'); + if (false === $data) { + $json_path = plugin_dir_path(__FILE__) . 'admin/assets/data/matomo-searchengine.json'; + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local plugin file, WP_Filesystem not needed + $json = @file_get_contents($json_path); + $data = json_decode($json, true); + if (!is_array($data)) { + $data = []; + } + set_transient('slimstat_matomo_searchengine', $data, WEEK_IN_SECONDS); + } + + $cached_search_engines = $data; + return $cached_search_engines; + } + // end get_search_engines + + /** * Toggles WordPress filters on date_i18n function */ public static function toggle_date_i18n_filters($_turn_on = true) @@ -1202,7 +782,7 @@ { return [ 'version' => SLIMSTAT_ANALYTICS_VERSION, - 'secret' => wp_hash(uniqid(time(), true)), + 'secret' => wp_hash(wp_generate_password(64, true, true)), 'browscap_last_modified' => 0, // General @@ -1211,28 +791,40 @@ // General - Tracker 'is_tracking' => 'on', 'track_admin_pages' => 'no', - 'javascript_mode' => 'on', + 'javascript_mode' => 'off', // Changed: Enable server-side tracking by default // General - WordPress Integration 'add_dashboard_widgets' => 'on', - 'use_separate_menu' => 'no', + 'use_separate_menu' => 'on', 'add_posts_column' => 'no', 'posts_column_pageviews' => 'on', + 'display_notifications' => 'on', // General - Database - 'auto_purge' => 0, + 'auto_purge' => 420, 'auto_purge_delete' => 'on', // Tracker // ----------------------------------------------------------------------- // Tracker - Data Protection - 'anonymize_ip' => 'no', - 'set_tracker_cookie' => 'on', - 'display_opt_out' => 'no', - 'opt_out_cookie_names' => '', - 'opt_in_cookie_names' => '', - 'opt_out_message' => '<p style="display:block;position:fixed;left:0;bottom:0;margin:0;padding:1em 2em;background-color:#eee;width:100%;z-index:99999;">This website stores cookies on your computer. These cookies are used to provide a more personalized experience and to track your whereabouts around our website in compliance with the European General Data Protection Regulation. If you decide to to opt-out of any future tracking, a cookie will be setup in your browser to remember this choice for one year.<br><br><a href="#" onclick="javascript:SlimStat.optout(event, false);">Accept</a> or <a href="#" onclick="javascript:SlimStat.optout(event, true);">Deny</a></p>', + // anonymize_ip: mask IP before storing; hash_ip: generate daily visitor_id based on masked IP + UA + 'gdpr_enabled' => 'on', // Changed: Enable GDPR by default for safety + 'anonymize_ip' => 'on', // Changed: Anonymize IPs by default + 'hash_ip' => 'on', // Changed: Hash IPs by default + 'set_tracker_cookie' => 'off', // Changed: Don't set cookies by default (GDPR-safe) + 'use_slimstat_banner' => 'on', // Changed: Enable banner by default when GDPR is enabled + 'consent_integration' => 'slimstat_banner', // Changed: Use SlimStat banner by default when GDPR is enabled + 'consent_level_integration'=> 'statistics', + 'opt_out_message' => '', + 'gdpr_accept_button_text' => __('Accept', 'wp-slimstat'), + 'gdpr_decline_button_text' => __('Decline', 'wp-slimstat'), + 'gdpr_theme_mode' => 'auto', // 'light', 'dark', 'auto' + 'anonymous_tracking' => 'off', // Changed: Enable anonymous tracking by default + 'do_not_track' => 'off', + 'display_opt_out' => 'no', + 'opt_out_cookie_names' => '', + 'opt_in_cookie_names' => '', // Tracker - Link Tracking 'track_same_domain_referers' => 'no', @@ -1275,7 +867,7 @@ 'mozcom_secret_key' => '', 'show_complete_user_agent_tooltip' => 'no', 'async_load' => 'no', - 'limit_results' => '1000', + 'limit_results' => '200', 'enable_sov' => 'no', // Exclusions @@ -1319,7 +911,7 @@ 'can_admin' => '', // Access Control - REST API - 'rest_api_tokens' => wp_hash(uniqid(time() - 3600, true)), + 'rest_api_tokens' => wp_hash(wp_generate_password(64, true, true)), // Maintenance // ----------------------------------------------------------------------- @@ -1336,7 +928,6 @@ 'notice_browscap' => 'on', 'notice_geolite' => 'on', 'notice_caching' => 'on', - 'notice_translate' => 'on', // Network-wide Settings 'locked_options' => '', @@ -1365,17 +956,24 @@ // Use the new unified tracking method setting $method = self::$settings['tracking_request_method'] ?? 'rest'; + // Handle legacy 'adblock' value (renamed to 'adblock_bypass' in v5.3.0) + if ( 'adblock' === $method ) { + $method = 'adblock_bypass'; + } + // Prepare URLs for all methods $rest_url = rest_url('slimstat/v1/hit'); + $rest_base_url = rest_url(); $ajax_url = admin_url('admin-ajax.php'); $ajax_url_relative = admin_url('admin-ajax.php', 'relative'); - $adblock_hash = md5(site_url() . 'slimstat_request' . SLIMSTAT_ANALYTICS_VERSION); + $adblock_hash = \SlimStat\Providers\RestApiManager::getSecureAdblockHash(); $adblock_url = home_url(sprintf('request/%s/', $adblock_hash)); // Always provide all possible endpoints for fallback logic $params = [ 'transport' => $method, 'ajaxurl_rest' => $rest_url, + 'resturl' => $rest_base_url, 'ajaxurl_ajax' => ('on' == self::$settings['ajax_relative_path']) ? $ajax_url_relative : $ajax_url, 'ajaxurl_adblock' => $adblock_url, ]; @@ -1387,8 +985,8 @@ $params['ajaxurl'] = ('on' == self::$settings['ajax_relative_path']) ? $ajax_url_relative : $ajax_url; } elseif ('adblock_bypass' === $method) { $params['ajaxurl'] = $adblock_url; - // Also set transport to 'adblock' for JS clarity - $params['transport'] = 'adblock'; + // Also set transport to 'adblock_bypass' for JS clarity + $params['transport'] = 'adblock_bypass'; } else { $params['ajaxurl'] = $rest_url; } @@ -1400,37 +998,69 @@ $params['dnt'] = str_replace(' ', '', self::$settings['do_not_track_outbound_classes_rel_href']); } - if ('on' == self::$settings['display_opt_out']) { - $params['oc'] = ['slimstat_optout_tracking']; - if (!empty(self::$settings['opt_out_cookie_names'])) { - foreach (self::string_to_array(self::$settings['opt_out_cookie_names']) as $a_cookie_pair) { - $params['oc'][] = substr($a_cookie_pair, 0, strpos($a_cookie_pair, '=')); - } - } - $params['oc'] = implode(',', $params['oc']); - } + // Internal GDPR banner is optionally available alongside CMP integrations. if ('on' != self::$settings['javascript_mode']) { if (empty(self::$stat['id']) || intval(self::$stat['id']) < 0) { return false; } - $params['id'] = self::_get_value_with_checksum(intval(self::$stat['id'])); + $params['id'] = \SlimStat\Tracker\Utils::getValueWithChecksum(intval(self::$stat['id'])); } else { - $params['ci'] = self::_get_value_with_checksum(self::_base64_url_encode(serialize(self::_get_content_info()))); + $params['ci'] = \SlimStat\Tracker\Utils::getValueWithChecksum(\SlimStat\Tracker\Utils::base64UrlEncode(wp_json_encode(\SlimStat\Tracker\Utils::getContentInfo()))); } $params['wp_rest_nonce'] = wp_create_nonce('wp_rest'); + // Expose consent/DNT info to client + $params['wp_consent_integration'] = (self::$settings['consent_integration'] ?? '') === 'wp_consent_api' ? 'enabled' : 'disabled'; + $params['consent_integration'] = self::$settings['consent_integration'] ?? ''; + $params['consent_level_integration'] = (self::$settings['consent_level_integration'] ?? 'statistics'); + $params['respect_dnt'] = self::$settings['do_not_track'] ?? 'off'; + $gdpr_enabled_setting = strtolower((string) (self::$settings['gdpr_enabled'] ?? 'on')); + $params['gdpr_enabled'] = in_array($gdpr_enabled_setting, ['off', 'no', 'false', '0'], true) ? 'off' : 'on'; + $params['anonymous_tracking'] = self::$settings['anonymous_tracking'] ?? 'off'; + $params['anonymize_ip'] = self::$settings['anonymize_ip'] ?? 'no'; + $params['hash_ip'] = self::$settings['hash_ip'] ?? 'no'; + $params['set_tracker_cookie'] = self::$settings['set_tracker_cookie'] ?? 'on'; + $params['use_slimstat_banner'] = self::$settings['use_slimstat_banner'] ?? 'off'; + + if ('on' === $params['use_slimstat_banner']) { + // Set GDPR consent endpoint based on tracking method + if ('rest' === $method) { + $params['gdpr_consent_endpoint'] = rest_url('slimstat/v1/gdpr/consent'); + } elseif ('ajax' === $method) { + $params['gdpr_consent_endpoint'] = ('on' == self::$settings['ajax_relative_path']) ? $ajax_url_relative : $ajax_url; + } elseif ('adblock_bypass' === $method) { + $params['gdpr_consent_endpoint'] = $adblock_url; + } else { + $params['gdpr_consent_endpoint'] = rest_url('slimstat/v1/gdpr/consent'); + } + $params['gdpr_cookie_name'] = \SlimStat\Services\GDPRService::CONSENT_COOKIE_NAME; + $params['gdpr_cookie_path'] = defined('COOKIEPATH') ? COOKIEPATH : '/'; + $params['gdpr_consent_method'] = $method; + } $params = apply_filters('slimstat_js_params', $params); + // Add dependencies for consent integrations (e.g., WP Consent API) + $dependencies = []; + if ((self::$settings['consent_integration'] ?? '') === 'wp_consent_api') { + $dependencies[] = 'wp-consent-api'; + } + // Register the correct script for adblock bypass, CDN, or default + $local_script_version = SLIMSTAT_ANALYTICS_VERSION; + $local_script_path = plugin_dir_path(__FILE__) . 'wp-slimstat.min.js'; + if (file_exists($local_script_path)) { + $local_script_version .= '.' . filemtime($local_script_path); + } + if ('adblock_bypass' === $method) { - $hash = md5(site_url() . 'slimstat'); - wp_register_script('wp_slimstat', home_url(sprintf('/%s.js/', $hash)), [], SLIMSTAT_ANALYTICS_VERSION, true); + $hash_js = md5(site_url() . 'slimstat'); + wp_register_script('wp_slimstat', home_url(sprintf('/%s.js/', $hash_js)), $dependencies, SLIMSTAT_ANALYTICS_VERSION, true); } elseif ('on' == self::$settings['enable_cdn']) { - wp_register_script('wp_slimstat', 'https://cdn.jsdelivr.net/wp/wp-slimstat/tags/' . SLIMSTAT_ANALYTICS_VERSION . '/wp-slimstat.min.js', [], null, true); + wp_register_script('wp_slimstat', 'https://cdn.jsdelivr.net/wp/wp-slimstat/tags/' . SLIMSTAT_ANALYTICS_VERSION . '/wp-slimstat.min.js', $dependencies, null, true); } else { - wp_register_script('wp_slimstat', plugins_url('/wp-slimstat.min.js', __FILE__), [], SLIMSTAT_ANALYTICS_VERSION, true); + wp_register_script('wp_slimstat', plugins_url('/wp-slimstat.min.js', __FILE__), $dependencies, $local_script_version, true); } wp_enqueue_script('wp_slimstat'); @@ -1448,11 +1078,56 @@ } wp_localize_script('wp_slimstat', 'SlimStatParams', $params); + return null; } // end enqueue_tracker + /** + * Enqueue assets for the internal SlimStat GDPR banner. + * + * @return void + */ + public static function enqueue_gdpr_assets() + { + if ('on' !== (self::$settings['use_slimstat_banner'] ?? 'off')) { + return; + } + + wp_enqueue_style( + 'wp_slimstat_gdpr_banner', + plugins_url('/assets/css/gdpr-banner.css', __FILE__), + [], + SLIMSTAT_ANALYTICS_VERSION + ); + } + + /** + * Render the SlimStat GDPR banner markup. + * + * @return void + */ + public static function render_gdpr_banner() + { + if ('on' !== (self::$settings['use_slimstat_banner'] ?? 'off')) { + return; + } + + if (is_admin() && !wp_doing_ajax()) { + return; + } + + $gdpr_service = new \SlimStat\Services\GDPRService(self::$settings); + $banner_html = $gdpr_service->getBannerHtml(); + + if ('' === $banner_html) { + return; + } + + echo $banner_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Sanitized in GDPRService + } + public static function add_defer_to_script_tag($_tag, $_handle) { if ('wp_slimstat' === $_handle && false === stripos($_tag, 'defer')) { @@ -1473,77 +1148,137 @@ return; } - $days_ago = strtotime(self::date_i18n('Y-m-d H:i:s') . sprintf(' -%d days', $autopurge_interval)); + $days_ago = strtotime(self::date_i18n('Y-m-d H:i:s') . sprintf(' -%d days', $autopurge_interval)); + $table_stats = $GLOBALS['wpdb']->prefix . 'slim_stats'; + $table_stats_archive = $GLOBALS['wpdb']->prefix . 'slim_stats_archive'; + $table_events = $GLOBALS['wpdb']->prefix . 'slim_events'; + $table_events_archive = $GLOBALS['wpdb']->prefix . 'slim_events_archive'; // Copy entries to the archive table, if needed if ('no' != self::$settings['auto_purge_delete']) { - $is_copy_done = self::$wpdb->query(" - INSERT INTO {$GLOBALS['wpdb']->prefix}slim_stats_archive (id, ip, other_ip, username, email, country, location, city, referer, resource, searchterms, notes, visit_id, server_latency, page_performance, browser, browser_version, browser_type, platform, language, fingerprint, user_agent, resolution, screen_width, screen_height, content_type, category, author, content_id, tz_offset, outbound_resource, dt_out, dt) - SELECT id, ip, other_ip, username, email, country, location, city, referer, resource, searchterms, notes, visit_id, server_latency, page_performance, browser, browser_version, browser_type, platform, language, fingerprint, user_agent, resolution, screen_width, screen_height, content_type, category, author, content_id, tz_offset, outbound_resource, dt_out, dt - FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats - WHERE dt < {$days_ago}"); - + // Use Query builder for INSERT INTO ... SELECT ... with prepared statements + $insert_sql = self::$wpdb->prepare( + "INSERT INTO {$table_stats_archive} (id, ip, other_ip, username, email, country, location, city, referer, resource, searchterms, notes, visit_id, server_latency, page_performance, browser, browser_version, browser_type, platform, language, fingerprint, user_agent, resolution, screen_width, screen_height, content_type, category, author, content_id, tz_offset, outbound_resource, dt_out, dt) SELECT id, ip, other_ip, username, email, country, location, city, referer, resource, searchterms, notes, visit_id, server_latency, page_performance, browser, browser_version, browser_type, platform, language, fingerprint, user_agent, resolution, screen_width, screen_height, content_type, category, author, content_id, tz_offset, outbound_resource, dt_out, dt FROM {$table_stats} WHERE dt < %d", + $days_ago + ); + $is_copy_done = self::$wpdb->query($insert_sql); if (false !== $is_copy_done) { - self::$wpdb->query(sprintf('DELETE ts FROM %sslim_stats ts WHERE ts.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago)); + \SlimStat\Utils\Query::delete($table_stats)->where('dt', '<', $days_ago)->execute(); } - - $is_copy_done = self::$wpdb->query( - " - INSERT INTO {$GLOBALS['wpdb']->prefix}slim_events_archive (type, event_description, notes, position, id, dt) - SELECT type, event_description, notes, position, id, dt - FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_events - WHERE dt < {$days_ago}" + $insert_sql_events = self::$wpdb->prepare( + "INSERT INTO {$table_events_archive} (type, event_description, notes, position, id, dt) SELECT type, event_description, notes, position, id, dt FROM {$table_events} WHERE dt < %d", + $days_ago ); - + $is_copy_done = self::$wpdb->query($insert_sql_events); if (false !== $is_copy_done) { - self::$wpdb->query(sprintf('DELETE te FROM %sslim_events te WHERE te.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago)); + \SlimStat\Utils\Query::delete($table_events)->where('dt', '<', $days_ago)->execute(); } } else { // Delete old entries - self::$wpdb->query(sprintf('DELETE ts FROM %sslim_stats ts WHERE ts.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago)); - self::$wpdb->query(sprintf('DELETE te FROM %sslim_events te WHERE te.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago)); + \SlimStat\Utils\Query::delete($table_stats)->where('dt', '<', $days_ago)->execute(); + \SlimStat\Utils\Query::delete($table_events)->where('dt', '<', $days_ago)->execute(); } - // Optimize tables - self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_stats', $GLOBALS[ 'wpdb' ]->prefix)); - self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_stats_archive', $GLOBALS[ 'wpdb' ]->prefix)); - self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_events', $GLOBALS[ 'wpdb' ]->prefix)); - self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_events_archive', $GLOBALS[ 'wpdb' ]->prefix)); + // Optimize tables (keep as direct queries) + self::$wpdb->query('OPTIMIZE TABLE ' . $table_stats); + self::$wpdb->query('OPTIMIZE TABLE ' . $table_stats_archive); + self::$wpdb->query('OPTIMIZE TABLE ' . $table_events); + self::$wpdb->query('OPTIMIZE TABLE ' . $table_events_archive); } public static function wp_slimstat_update_geoip_database() { - $this_update = strtotime('first Tuesday of this month') + (86400 * 2); + // Calculate the most recent "first Tuesday + 2 days" that has already passed + $this_month_update = strtotime('first Tuesday of this month') + (86400 * 2); + $current_time = time(); + + // If this month's update window hasn't arrived yet, use last month's window + if ($current_time < $this_month_update) { + $this_update = strtotime('first Tuesday of last month') + (86400 * 2); + } else { + $this_update = $this_month_update; + } + $last_update = get_option('slimstat_last_geoip_dl', 0); if ($last_update < $this_update) { - $geographicProvider = new \SlimStat\Services\GeoService(); + // Determine which geolocation provider to use + $provider = self::$settings['geolocation_provider'] ?? 'dbip'; + + $geographicProvider = new \SlimStat\Services\Geolocation\GeolocationService($provider, []); try { - $geographicProvider - ->setEnableMaxmind(wp_slimstat::$settings['enable_maxmind']) - ->setUpdate(true) - ->setMaxmindLicense(wp_slimstat::$settings['maxmind_license_key']) - ->download(); + $geographicProvider->updateDatabase(); // Set the last update time - $geographicProvider->updateLastUpdateTime(time()); + update_option('slimstat_last_geoip_dl', time()); } catch (\Exception $e) { - $geographicProvider->logError($e->getMessage()); + wp_slimstat::log('Geolocation database update failed: ' . $e->getMessage(), 'error'); } } } /** - * Displays the opt-out box via Ajax request + * Register privacy policy content for WordPress Privacy Tools + * + * @since 5.4.0 */ - public static function get_optout_html() + public static function registerPrivacyPolicyContent() { - die(stripslashes(self::$settings['opt_out_message'])); - } + if (!function_exists('wp_add_privacy_policy_content')) { + return; + } - // end get_optout_html + $content = '<h2>' . __('SlimStat Analytics', 'wp-slimstat') . '</h2>'; + $content .= '<p><strong>' . __('What personal data we collect and why', 'wp-slimstat') . '</strong></p>'; + $content .= '<p>' . __('SlimStat Analytics collects the following data about website visitors:', 'wp-slimstat') . '</p>'; + $content .= '<ul>'; + $content .= '<li>' . __('IP Address: Collected for analytics and security purposes. May be anonymized or hashed based on your privacy settings.', 'wp-slimstat') . '</li>'; + $content .= '<li>' . __('Page URLs: Tracks which pages are visited to analyze website usage.', 'wp-slimstat') . '</li>'; + $content .= '<li>' . __('Referrer Information: Tracks where visitors came from (search engines, other websites, etc.).', 'wp-slimstat') . '</li>'; + $content .= '<li>' . __('Browser and Device Information: User agent, screen resolution, and device type for analytics.', 'wp-slimstat') . '</li>'; + $content .= '<li>' . __('Timestamp: Date and time of each page visit.', 'wp-slimstat') . '</li>'; + + if ('on' === (self::$settings['set_tracker_cookie'] ?? 'off')) { + $content .= '<li>' . __('Cookies: A tracking cookie is used to identify returning visitors and maintain session continuity.', 'wp-slimstat') . '</li>'; + } + + if ('on' !== (self::$settings['ignore_wp_users'] ?? 'off')) { + $content .= '<li>' . __('User Information: If you are logged in, your username and email may be associated with your visits (only with consent when GDPR mode is enabled).', 'wp-slimstat') . '</li>'; + } + + $content .= '</ul>'; + + $content .= '<p><strong>' . __('How long we retain your data', 'wp-slimstat') . '</strong></p>'; + $retention_days = intval(self::$settings['auto_purge'] ?? 420); + if ($retention_days > 0) { + $content .= '<p>' . sprintf(__('Analytics data is automatically deleted after %d days, in compliance with GDPR data retention requirements.', 'wp-slimstat'), $retention_days) . '</p>'; + } else { + $content .= '<p>' . __('Analytics data retention is currently disabled. Please contact the site administrator for information about data retention policies.', 'wp-slimstat') . '</p>'; + } + + $content .= '<p><strong>' . __('Your rights', 'wp-slimstat') . '</strong></p>'; + $content .= '<p>' . __('Under GDPR, you have the right to:', 'wp-slimstat') . '</p>'; + $content .= '<ul>'; + $content .= '<li>' . __('Access your personal data collected by SlimStat', 'wp-slimstat') . '</li>'; + $content .= '<li>' . __('Request deletion of your personal data (Right to be Forgotten)', 'wp-slimstat') . '</li>'; + $content .= '<li>' . __('Opt-out of tracking by revoking consent (if GDPR mode is enabled)', 'wp-slimstat') . '</li>'; + $content .= '</ul>'; + + if ('on' === (self::$settings['gdpr_enabled'] ?? 'on')) { + $content .= '<p>' . __('You can exercise these rights by using the WordPress Privacy Tools (Tools → Export Personal Data / Erase Personal Data) or by contacting the site administrator.', 'wp-slimstat') . '</p>'; + } + + $content .= '<p><strong>' . __('Consent Management', 'wp-slimstat') . '</strong></p>'; + if ('on' === (self::$settings['anonymous_tracking'] ?? 'off')) { + $content .= '<p>' . __('This website uses Anonymous Tracking Mode. Initial tracking occurs without collecting personally identifiable information (PII). Full tracking with PII collection only occurs after you grant explicit consent.', 'wp-slimstat') . '</p>'; + } else { + $content .= '<p>' . __('Tracking requires your consent when GDPR mode is enabled. You can grant or revoke consent at any time through the consent management interface.', 'wp-slimstat') . '</p>'; + } + + wp_add_privacy_policy_content('SlimStat Analytics', $content); + } public static function add_plugin_manual_download_link($_links = [], $_plugin_file = '') { @@ -1617,535 +1352,6 @@ // end get_lossy_url /** - * Update content type as needed - */ - public static function update_content_type($_status = 301, $_location = '') - { - if ($_status >= 300 && $_status < 400) { - // SEE WHY THIS DOESN'T WORK?! - self::$stat['content_type'] = 'redirect:' . intval($_status); - self::_update_row(self::$stat); - } - - return $_status; - } - // end update_content_type - - /** - * Stores the pageview information in the database and returns the ID associated to the new entry - */ - protected static function _insert_row($_data = [], $_table = '') - { - if (empty($_data) || empty($_table)) { - return -1; - } - - // Remove unwanted characters from keys (SQL injections, anyone?) - $data_keys = []; - foreach (array_keys($_data) as $a_key) { - $data_keys[] = sanitize_key($a_key); - } - - // Remove unwanted characters from data (SQL injections, anyone?) - foreach ($_data as $key => $value) { - $_data[$key] = 'resource' == $key ? sanitize_url($value) : sanitize_text_field($value); - } - - self::$wpdb->query(self::$wpdb->prepare(" - INSERT IGNORE INTO {$_table} (" . implode(', ', $data_keys) . ') - VALUES (' . substr(str_repeat('%s,', count($_data)), 0, -1) . ')', $_data)); - - return intval(self::$wpdb->insert_id); - } - // end _insert_row - - /** - * Updates an existing row - */ - protected static function _update_row($_data = []) - { - if (empty($_data) || empty($_data['id'])) { - return false; - } - - // Extract the ID from the array - $id = abs(intval($_data['id'])); - unset($_data['id']); - - // Sanitize column names (SQL/XSS injections, anyone?) - $_data = array_filter($_data); - - // The 'notes' column stores multiple comma-separated values: we need to append the new value to the existing ones - // Also, values are organized in an array, which we need to implode as a string - $notes = ''; - if (!empty($_data['notes']) && is_array($_data['notes'])) { - $notes = (count($_data) > 1 ? ',' : '') . "notes=CONCAT( IFNULL( notes, '' ), '[" . esc_sql(implode('][', $_data['notes'])) . "]' )"; - unset($_data['notes']); - } - - $prepared_query = self::$wpdb->prepare(" - UPDATE IGNORE {$GLOBALS[ 'wpdb' ]->prefix}slim_stats - SET " . implode('=%s,', array_keys($_data)) . "=%s - WHERE id = {$id} - ", $_data); - - // Add the notes - if ('' !== $notes && '0' !== $notes) { - $prepared_query = str_replace('WHERE id =', $notes . ' WHERE id =', $prepared_query); - } - - // Save the data in the database - self::$wpdb->query($prepared_query); - - return $id; - } - // end _update_row - - /** - * Tries to find the user's REAL IP address - */ - protected static function _get_remote_ip() - { - $ip_array = ['', '']; - - if (!empty($_SERVER['REMOTE_ADDR']) && false !== filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)) { - $ip_array[0] = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])); - } - - $originating_ip_headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR', 'HTTP_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_INCAP_CLIENT_IP']; - foreach ($originating_ip_headers as $a_header) { - if (!empty($_SERVER[$a_header])) { - foreach (explode(',', $_SERVER[$a_header]) as $a_ip) { - if (false !== filter_var($a_ip, FILTER_VALIDATE_IP) && $a_ip != $ip_array[0]) { - $ip_array[1] = $a_ip; - break; - } - } - } - } - - return apply_filters('slimstat_filter_ip_address', $ip_array); - } - // end _get_remote_ip - - /** - * Extracts the accepted language from browser headers - */ - protected static function _get_language() - { - if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - - // Capture up to the first delimiter (, found in Safari) - preg_match('/([^,;]*)/', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $array_languages); - - // Fix some codes, the correct syntax is with minus (-) not underscore (_) - return str_replace('_', '-', strtolower($array_languages[0])); - } - return ''; // Indeterminable language - } - // end _get_language - - /** - * Sniffs out referrals from search engines and tries to determine the query string - */ - protected static function _get_search_terms($_url = '') - { - if (empty($_url)) { - return ''; - } - - $searchterms = ''; - - // Load the search engines list to mark pageviews accordingly - // Each entry contains the following attributes - // - params: which query string params is associated to the search keyword - // - backlink: format of the URL point to the search engine result page - // - charsets: list of charset used to encode the keywords - // - $search_engines = file_get_contents(plugin_dir_path(__FILE__) . 'admin/assets/data/matomo-searchengine.json'); - $search_engines = json_decode($search_engines, true); - - $parsed_url = @parse_url($_url); - - if (empty($search_engines) || empty($parsed_url) || empty($parsed_url['host'])) { - return ''; - } - - $sek = self::get_lossy_url($parsed_url['host']); - - if (!empty($search_engines[$sek])) { - if (empty($search_engines[$sek]['params'])) { - $search_engines[$sek]['params'] = ['q']; - } - - foreach ($search_engines[$sek]['params'] as $a_param) { - if (!empty($parsed_url['query'])) { - $searchterms = self::_get_param_from_query_string($parsed_url['query'], $a_param); - if (!empty($searchterms)) { - break; - } - } - } - - // Make sure to use the appropriate charset, if specified - if (!empty($searchterms) && (!empty($search_engines['charsets']) && function_exists('iconv'))) { - $charset = $search_engines['charsets'][0]; - if (count($search_engines['charsets']) > 1 && function_exists('mb_detect_encoding')) { - $charset = mb_detect_encoding($searchterms, $search_engines['charsets']); - if (false === $charset) { - $charset = $search_engines['charsets'][0]; - } - } - $new_searchterms = @iconv($charset, 'UTF-8//IGNORE', $searchterms); - if (!('' === $new_searchterms || '0' === $new_searchterms || false === $new_searchterms)) { - $searchterms = $new_searchterms; - } - } - } elseif (!empty($parsed_url['query'])) { - // We weren't lucky, but there's still hope - foreach (['ask', 'k', 'q', 'qs', 'qt', 'query', 's', 'string'] as $a_param) { - $searchterms = self::_get_param_from_query_string($parsed_url['query'], $a_param); - if (!empty($searchterms)) { - break; - } - } - } - - return sanitize_text_field($searchterms); - } - // end _get_search_terms - - /** - * Retrieves a param value from a string treated as a URL query string - */ - protected static function _get_param_from_query_string($_query = '', $_parameter = '') - { - if (empty($_query)) { - return ''; - } - - @parse_str($_query, $values); - - return empty($values[$_parameter]) ? '' : $values[$_parameter]; - } - // end _get_param_from_query_string - - /** - * Returns details about the resource being accessed - */ - protected static function _get_content_info() - { - $content_info = ['content_type' => '']; - - // Mark 404 pages - if (is_404()) { - $content_info['content_type'] = '404'; - } // Type - elseif (is_single()) { - if (($post_type = get_post_type()) != 'post') { - $post_type = 'cpt:' . $post_type; - } - - $content_info['content_type'] = $post_type; - $category_ids = []; - foreach (get_object_taxonomies($GLOBALS['post']) as $a_taxonomy) { - $terms = get_the_terms($GLOBALS['post']->ID, $a_taxonomy); - if (is_array($terms)) { - foreach ($terms as $a_term) { - $category_ids[] = $a_term->term_id; - } - $content_info['category'] = implode(',', $category_ids); - } - } - $content_info['content_id'] = $GLOBALS['post']->ID; - } elseif (is_page()) { - $content_info['content_type'] = 'page'; - $content_info['content_id'] = $GLOBALS['post']->ID; - } elseif (is_attachment()) { - $content_info['content_type'] = 'attachment'; - } elseif (is_singular()) { - $content_info['content_type'] = 'singular'; - } elseif (is_post_type_archive()) { - $content_info['content_type'] = 'post_type_archive'; - } elseif (is_tag()) { - $content_info['content_type'] = 'tag'; - $list_tags = get_the_tags(); - if (is_array($list_tags)) { - $tag_info = array_pop($list_tags); - if (!empty($tag_info)) { - $content_info['category'] = $tag_info->term_id; - } - } - } elseif (is_tax()) { - $content_info['content_type'] = 'taxonomy'; - } elseif (is_category()) { - $content_info['content_type'] = 'category'; - $list_categories = get_the_category(); - if (is_array($list_categories)) { - $cat_info = array_pop($list_categories); - if (!empty($cat_info)) { - $content_info['category'] = $cat_info->term_id; - } - } - } elseif (is_date()) { - $content_info['content_type'] = 'date'; - } elseif (is_author()) { - $content_info['content_type'] = 'author'; - } elseif (is_archive()) { - $content_info['content_type'] = 'archive'; - } elseif (is_search()) { - $content_info['content_type'] = 'search'; - } elseif (is_feed()) { - $content_info['content_type'] = 'feed'; - } elseif (is_home() || is_front_page()) { - $content_info['content_type'] = 'home'; - } elseif (!empty($GLOBALS['pagenow']) && 'wp-login.php' == $GLOBALS['pagenow']) { - $content_info['content_type'] = 'login'; - } elseif (!empty($GLOBALS['pagenow']) && 'wp-register.php' == $GLOBALS['pagenow']) { - $content_info['content_type'] = 'registration'; - } // WordPress sets is_admin() to true for all ajax requests ( front-end or admin-side ) - elseif (is_admin() && (!defined('DOING_AJAX') || !DOING_AJAX)) { - $content_info['content_type'] = 'admin'; - } - - if (is_paged()) { - $content_info['content_type'] .= ':paged'; - } - - // Author - if (is_singular()) { - $author = get_the_author_meta('user_login', $GLOBALS['post']->post_author); - if (!empty($author)) { - $content_info['author'] = $author; - } - } - - return $content_info; - } - // end _get_content_info - - /** - * Reads the information sent by the Javascript tracker and adds it to the $_stat array - */ - protected static function _get_client_info($_data_js = [], $_stat = []) - { - if (!empty($_data_js['bw'])) { - $_stat['resolution'] = strip_tags(trim($_data_js['bw'] . 'x' . $_data_js['bh'])); - } - if (!empty($_data_js['sw'])) { - $_stat['screen_width'] = intval($_data_js['sw']); - } - if (!empty($_data_js['sh'])) { - $_stat['screen_height'] = intval($_data_js['sh']); - } - if (!empty($_data_js['sl']) && $_data_js['sl'] > 0 && $_data_js['sl'] < 60000) { - $_stat['server_latency'] = intval($_data_js['sl']); - } - if (!empty($_data_js['pp']) && $_data_js['pp'] > 0 && $_data_js['pp'] < 60000) { - $_stat['page_performance'] = intval($_data_js['pp']); - } - if (!empty($_data_js['fh']) && 'on' != self::$settings['anonymize_ip']) { - $_stat['fingerprint'] = sanitize_text_field($_data_js['fh']); - } - if (!empty($_data_js['tz'])) { - $_stat['tz_offset'] = intval($_data_js['tz']); - } - - return $_stat; - } - // end _get_client_info - - /** - * Reads the cookie to get the visit_id and sets the variable accordingly - */ - protected static function _set_visit_id($_force_assign = false) - { - $is_new_session = true; - $identifier = 0; - - if (isset($_COOKIE['slimstat_tracking_code'])) { - // Make sure only authorized information is recorded - $identifier = self::_get_value_without_checksum($_COOKIE['slimstat_tracking_code']); - if (false === $identifier) { - return false; - } - - $is_new_session = (false !== strpos($identifier, 'id')); - $identifier = intval($identifier); - } - - // User doesn't have an active session - if ($is_new_session && ($_force_assign || 'on' == self::$settings['javascript_mode'])) { - if (empty(self::$settings['session_duration'])) { - self::$settings['session_duration'] = 1800; - } - - self::$stat['visit_id'] = get_transient('slimstat_visit_id'); - if (false === self::$stat['visit_id']) { - self::$stat['visit_id'] = intval(self::$wpdb->get_var(sprintf('SELECT MAX( visit_id ) FROM %sslim_stats', $GLOBALS[ 'wpdb' ]->prefix))); - } - self::$stat['visit_id']++; - set_transient('slimstat_visit_id', self::$stat['visit_id']); - - $set_cookie = apply_filters('slimstat_set_visit_cookie', (!empty(self::$settings['set_tracker_cookie']) && 'on' == self::$settings['set_tracker_cookie'])); - if ($set_cookie) { - @setcookie( - 'slimstat_tracking_code', - self::_get_value_with_checksum(self::$stat['visit_id']), - ['expires' => time() + self::$settings['session_duration'], 'path' => COOKIEPATH] - ); - } - - } elseif ($identifier > 0) { - self::$stat['visit_id'] = $identifier; - } - - if ($is_new_session && $identifier > 0) { - self::$wpdb->query(self::$wpdb->prepare( - " - UPDATE {$GLOBALS['wpdb' ]->prefix}slim_stats - SET visit_id = %d - WHERE id = %d AND visit_id = 0", - self::$stat['visit_id'], - $identifier - )); - } - return ($is_new_session && ($_force_assign || 'on' == self::$settings['javascript_mode'])); - } - // end _set_visit_id - - /** - * Saves an error detected by the tracker in the database - */ - protected static function _log_error($_error_code = 0) - { - // Save this error in the database - self::update_option('slimstat_tracker_error', [$_error_code, self::date_i18n('U')]); - - // Allow third-party code to trigger actions based on this error - do_action('slimstat_track_exit_' . abs($_error_code), self::$stat); - - return -$_error_code; - } - - // end _log_error - - protected static function _get_value_with_checksum($_value = 0) - { - return $_value . '.' . md5($_value . self::$settings['secret']); - } - - protected static function _get_value_without_checksum($_value_with_checksum = '') - { - [$value, $checksum] = explode('.', $_value_with_checksum); - - if ($checksum === md5($value . self::$settings['secret'])) { - return $value; - } - - return false; - } - - /** - * Determines if a given string is listed in the corresponding 'exclusion' field - */ - protected static function _is_blacklisted($_needles = [], $_haystack_string = '') - { - foreach (self::string_to_array($_haystack_string) as $a_item) { - $pattern = str_replace(['\*', '\!'], ['(.*)', '.'], preg_quote($a_item, '@')); - - if (!is_array($_needles)) { - $_needles = [$_needles]; - } - - foreach ($_needles as $a_needle) { - if (preg_match(sprintf('@^%s$@i', $pattern), $a_needle)) { - return true; - } - } - } - - return false; - } - // end _is_blacklisted - - /** - * Determines if this is a new visitor, meaning that we've never seen this fingerprint before - */ - protected static function _is_new_visitor($_fingerprint = '') - { - // If the privacy option is enabled, all visitors would be considered "new"... - if ('on' == self::$settings['anonymize_ip']) { - return false; - } - - $count_fingerprint = self::$wpdb->get_var(self::$wpdb->prepare( - " - SELECT COUNT( id ) - FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats - WHERE fingerprint = %s", - $_fingerprint - )); - - return 0 == $count_fingerprint; - } - // end _is_new_visitor - - /** - * Validates and unpacks an IP Address - */ - protected static function _dtr_pton($_ip) - { - $unpacked = false; - - if (filter_var($_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - $unpacked = unpack('A4', inet_pton($_ip)); - } elseif (filter_var($_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && defined('AF_INET6')) { - $unpacked = unpack('A16', inet_pton($_ip)); - } - - $binary_ip = ''; - if ([] !== $unpacked && false !== $unpacked && isset($unpacked[1])) { - $unpacked = str_split($unpacked[1]); - foreach ($unpacked as $char) { - $binary_ip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT); - } - } - - return $binary_ip; - } - // end _dtr_pton - - /** - * Helper function to determine if we should ignore visits coming from this IP address - */ - protected static function _get_mask_length($ip) - { - if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - return 32; - } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - return 128; - } - - return false; - } - // end _get_mask_length - - /** - * These two functions here implement an URL-safe base64 string - */ - protected static function _base64_url_encode($_input = '') - { - return strtr(base64_encode($_input), '+/=', '._-'); - } - - protected static function _base64_url_decode($_input = '') - { - return strip_tags(trim(base64_decode(strtr($_input, '._-', '+/=')))); - } - // end _base64_url_encode/decode - - /** * Check if slimstat pro plugin is installed */ public static function pro_is_installed($pluginSlug = 'wp-slimstat-pro/wp-slimstat-pro.php') @@ -2216,7 +1422,7 @@ ], $_instance)); if (!empty($slimstat_widget_title)) { - echo (empty($_args['before_title']) ? '<h2 class="widget-title">' : $_args['before_title']) . $slimstat_widget_title . (empty($_args['after_title']) ? '</h2>' : $_args['after_title']); + echo (empty($_args['before_title']) ? '<h2 class="widget-title">' : $_args['before_title']) . esc_html($slimstat_widget_title) . (empty($_args['after_title']) ? '</h2>' : $_args['after_title']); } if (!empty($slimstat_widget_id)) { echo do_shortcode(sprintf("[slimstat f='widget' w='%s']%s[/slimstat]", $slimstat_widget_id, $slimstat_widget_filters)); @@ -2279,19 +1485,34 @@ { $instance = $_old_instance; - $instance['slimstat_widget_id'] = $_new_instance['slimstat_widget_id']; - $instance['slimstat_widget_title'] = $_new_instance['slimstat_widget_title']; - $instance['slimstat_widget_filters'] = $_new_instance['slimstat_widget_filters']; + $instance['slimstat_widget_id'] = sanitize_key($_new_instance['slimstat_widget_id'] ?? ''); + $instance['slimstat_widget_title'] = sanitize_text_field(wp_unslash($_new_instance['slimstat_widget_title'] ?? '')); + $instance['slimstat_widget_filters'] = sanitize_textarea_field(wp_unslash($_new_instance['slimstat_widget_filters'] ?? '')); return $instance; } } +// Early initialize DB handle for add-ons that may access wp_slimstat::$wpdb before init() runs +if (empty(wp_slimstat::$wpdb) && isset($GLOBALS['wpdb'])) { + wp_slimstat::$wpdb = $GLOBALS['wpdb']; +} + // Ok, let's go, Sparky! if (function_exists('add_action')) { // Since we use sendBeacon, this function sends raw POST data, which does not populate the $_POST variable automatically - if ((!empty($_SERVER['HTTP_CONTENT_TYPE']) || !empty($_SERVER['CONTENT_TYPE'])) && [] === $_POST) { + $http_content_type = isset($_SERVER['HTTP_CONTENT_TYPE']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_CONTENT_TYPE'])) : ''; + $content_type = isset($_SERVER['CONTENT_TYPE']) ? sanitize_text_field(wp_unslash($_SERVER['CONTENT_TYPE'])) : ''; + if ((!empty($http_content_type) || !empty($content_type)) && [] === $_POST) { + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Required for reading php://input stream $raw_post_string = file_get_contents('php://input'); parse_str($raw_post_string, wp_slimstat::$raw_post_array); + + // Sanitize the action key from the raw body before using it + if (!empty(wp_slimstat::$raw_post_array['action'])) { + wp_slimstat::$raw_post_array['action'] = sanitize_key( + wp_unslash(wp_slimstat::$raw_post_array['action']) + ); + } } elseif ([] !== $_POST) { wp_slimstat::$raw_post_array = $_POST; } @@ -2300,15 +1521,15 @@ if (!empty(wp_slimstat::$raw_post_array['action']) && 'slimtrack' == wp_slimstat::$raw_post_array['action']) { // This is needed because admin-ajax.php is reading $_REQUEST to fire the corresponding action + // Use a hardcoded literal instead of passing the user-supplied value if (empty($_POST['action'])) { - $_POST['action'] = wp_slimstat::$raw_post_array['action']; + $_POST['action'] = 'slimtrack'; } - add_action('wp_ajax_nopriv_slimtrack', ['wp_slimstat', 'slimtrack_ajax']); - add_action('wp_ajax_slimtrack', ['wp_slimstat', 'slimtrack_ajax']); + add_action('wp_ajax_nopriv_slimtrack', [\SlimStat\Tracker\Ajax::class, 'handle']); + add_action('wp_ajax_slimtrack', [\SlimStat\Tracker\Ajax::class, 'handle']); } - include_once(plugin_dir_path(__FILE__) . 'src/Constants.php'); // From the codex: You can't call register_activation_hook() inside a function hooked to the 'plugins_loaded' or 'init' hooks (or any other hook). These hooks are called before the plugin is loaded or activated. if (is_admin()) { @@ -2319,8 +1540,35 @@ add_action('widgets_init', ['wp_slimstat', 'register_widget']); + // Load textdomain early (before init at priority 20) + add_action('plugins_loaded', ['wp_slimstat', 'load_textdomain'], 10); + // Add the appropriate actions add_action('plugins_loaded', ['wp_slimstat', 'init'], 20); // Add the action to fetch chart data add_action('wp_ajax_slimstat_fetch_chart_data', [\SlimStat\Modules\Chart::class, 'ajaxFetchChartData']); } + +add_action('wp_ajax_slimstat_clear_cache', 'wp_slimstat_clear_cache_handler'); + +function wp_slimstat_clear_cache_handler() +{ + if (!current_user_can('manage_options')) { + wp_send_json_error(__('Permission denied', 'wp-slimstat')); + } + // Optional: check nonce if you add it to JS + if (empty($_POST['security']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['security'])), 'slimstat_clear_cache')) { + wp_send_json_error(__('Invalid nonce', 'wp-slimstat')); + } + + global $wpdb; + $transients = $wpdb->get_col( + sprintf("SELECT option_name FROM %s WHERE option_name LIKE '_transient_wp_slimstat_query_%%' OR option_name LIKE '_transient_timeout_wp_slimstat_query_%%'", $wpdb->options) + ); + $count = 0; + foreach ($transients as $transient) { + delete_option($transient); + $count++; + } + wp_send_json_success(sprintf(__('Slimstat cache cleared (%d items)', 'wp-slimstat'), $count)); +}
Exploit Outline
An unauthenticated attacker first extracts a tracking nonce from the 'SlimStatParams' global JavaScript object localized on the site's frontend. They then send a POST request to the tracking endpoint (either the 'slimstat_track' action via admin-ajax.php or the REST API tracking route) with a malicious JavaScript payload in the 'fh' parameter. This payload is stored in the plugin's tracking table in the database. The XSS is triggered when an administrator navigates to the 'Access Log' or other report pages in the WordPress dashboard that display visitor fingerprints, causing the injected script to execute in their browser context.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.