=== RISE2 Cookie Consent ===
Contributors: rise2studio
Tags: cookies, gdpr, cookie-consent, privacy, eprivacy, consent-mode
Requires at least: 6.0
Tested up to: 6.7
Requires PHP: 8.0
Stable tag: 2.18.0
License: GPLv2 or later

Self-hosted GDPR/ePrivacy cookie consent plugin: consent banner and settings panel, cookie declaration, automatic script and embed blocking, Google Consent Mode v2, consent records, Croatian and English texts.

== Description ==

Self-hosted cookie consent management with no subscription. Settings, the cookie declaration and the consent records stay in your own WordPress database.

Outside connections: the optional deep scan sends your site's address to the RISE2 cookie scanner service, which needs its URL and an API key (Scanner tab); the quick scan runs on your own server. Plugin updates are checked at plugins.rise2.studio.

= Core Features =
* Cookie scanning: a quick scan on your server and an optional deep scan in a real browser (RISE2 scanner service); recognises 70 known cookies (54 names, 16 name patterns); scheduled re-scans
* Automatic blocking, off until switched on: Google tags (gtag.js, Tag Manager), 56 script address patterns, 14 kinds of inline tracking call and 15 embed patterns (YouTube, Vimeo, Google Maps and others) — the full list is on the Tools tab
* Google Consent Mode v2 (basic or advanced, cache-compatible)
* Privacy policy generator (EN/HR)
* Consent versioning — force re-consent on policy changes, optionally automatic
* Consent records and a dashboard with Chart.js trends
* Croatian and English texts built in; every visitor text editable per language (Texts tab); WPML and Polylang string translation for the banner and category texts; TranslatePress for language detection
* Keyboard and screen-reader support: focus kept inside the settings panel and the Center Overlay, focus returned on close, visible focus on every control
* Import/Export settings as JSON
* 4 shortcodes: [r2cc_settings_button], [r2cc_cookie_table], [r2cc_providers], [r2cc_consent_status]
* Public JavaScript API (window.R2CookieConsent)

== Changelog ==

= 2.18.0 =
* Changed: blocking before consent is on for new installations (scripts and embeds), with a one-time notice to the administrator; existing sites keep their settings. Reset All Settings restores these defaults.
* Changed: the r2cc_consent_update dataLayer event is pushed with or without Google Consent Mode.
* Changed: the Customizer and page-builder previews (Elementor, Beaver Builder, Divi, Oxygen, WPBakery, Brizy, Thrive, Bricks) are not rewritten for editors; visitors are always blocked.
* New: recognised tracking pixels and Google Tag Manager's frame inside <noscript> are held back while Auto-block Scripts is on.
* New: the deep scan lists the localStorage and sessionStorage keys the pages write (keys only, never values).
* Fixed: reactivating, exporting or importing on a site whose embeds switch was never saved (installed before 2.10.0) could switch embed blocking off or on unexpectedly.

= 2.17.0 =
* New: keyboard and screen-reader support. Category names are buttons (Enter/Space open them, aria-expanded); the settings panel keeps Tab inside, closes with Esc and returns focus to the control that opened it; every control, the category switches included, has a visible focus ring.
* New: the Center Overlay is a modal dialog — it takes focus, keeps Tab inside, gives focus back after the choice, and scrolls inside on small or zoomed screens (every banner position has a maximum height). A corner banner takes focus only when nothing else has it; otherwise a polite live region announces it.
* Changed: [r2cc_consent_status] — several per page, refreshed after every choice, an expired or old-version choice shown as no longer valid, texts per language on the Texts tab (HR/EN built in), no inline script.
* Changed: R2CookieConsent.getConsent() returns null for a choice that no longer counts; R2CookieConsent.on() honours 'consent' and 'iframes-restored' and returns an unsubscribe function (other names keep subscribing to consent changes).
* Changed: accurate wording — exact blocking counts instead of "60+", the optional scanner service and its API key named, four shortcodes; the Croatian translation now covers every admin string.
* Removed: the unused stored setting "geo_restriction" (no field, no effect); deleted from every site after the update.

= 2.16.1 =
* Fixed: sites using WP Rocket's "Minify CSS/JS" kept serving an old minified copy of the banner's stylesheet after an update (new banner elements unstyled, colour settings ignored). The plugin's stylesheet and script now opt out of WP Rocket's minification; WP Fastest Cache's minified copies are cleared after an update.
* Fixed: after an update each site purges its own page cache on the first request that reaches WordPress, not only on the first wp-admin or admin-ajax request (an administrator, or a visitor's consent choice).
* Fixed: on WP Rocket 3.15.10 and later, a consent group's settings change refreshed only the first site's page cache; every site is purged again.

= 2.16.0 =
* New: data controllers — every company a consent is given to is named on the banner's first layer, with details in the settings panel and a privacy-policy link per site.
* New: providers and transfers — each cookie shows its provider, country and transfer safeguard (e.g. EU-US Data Privacy Framework, SCCs); the banner says when data may leave the EEA; new [r2cc_providers] shortcode. Pre-filled values are marked "needs review".
* New: consent versions — every banner a visitor can see is stored whole under a SHA-256 fingerprint and every consent record points to it; Consent Versions tab with JSON download. Consent records also store the site, the language and the consent version.
* New: Texts tab — every visitor-facing text per language, shared by a consent group; new recommended HR/EN wording (no "anonymous" identifier claims).
* New: optional automatic re-consent on material changes; retention period for consent records; consent records are kept on uninstall unless you choose otherwise.
* New: Google Consent Mode basic/advanced mode; blocked embeds offer "Load this content" and "Always allow"; custom blocking patterns; equal prominence of Reject and Accept.
* Multisite: subdirectory sites and mapped domains are handled correctly; the deep scan covers every site of the group; only the primary's and network administrators see the group's consent records; settings changes purge every site's page cache.
* Fixed: consents given on long-cached pages were not recorded; Safari asked again every 7 days; a choice made on an older banner pre-ticked the switches; the center-overlay banner could not be closed; inline GTM/Clarity snippets were not blocked and "ga(" matched theme code; withdrawal left Google Analytics cookies on the parent domain; the privacy-policy generator could overwrite a published page.
* Includes every 2.15.3 fix.

= 2.15.3 =
* Security/privacy: with Google Consent Mode on, a cached page could carry one visitor's "granted" as the default for every later visitor. The page head is now the same for everyone (everything denied) and a returning visitor's own choice is applied in the browser, before Google tags run.
* Fixed: IP anonymisation stored compressed and IPv4-mapped IPv6 addresses whole.
* Fixed (multisite consent group): subsites now show the primary site's banner on the front end. Until now every subsite used its own banner texts, colours, on/off switch and consent version for visitors, so one consent did not really cover the group; only wp-admin showed the shared values.
* Fixed (multisite): network activation now gives every site its default settings, so subsites no longer start with an empty banner; sites created later are set up too.
* Fixed (multisite): consent-version snapshots made on a subsite are kept on the primary site instead of being lost.
* Fixed (multisite): the list of websites shown in the banner is refreshed when sites are added or removed, also when the primary is the main site.
* Fixed: the "Consent group" switch on the Network tab was invisible.
* Fixed (multisite): deleting the plugin removes its data from every site, not only the main one; scans are scheduled on every site and removed on deactivation.
* Fixed: the English browser-language pack showed some texts in the site language; [r2cc_cookie_table] did not show WPML category translations.
* Fixed: the update check retried a down update server on every page load; the scan e-mail listed the wrong cookies; network-activated plugins were missed by the quick scan; saved texts gained backslashes before quotes.

= 2.15.2 =
* Fixed: "Export settings" (Tools tab) exported the settings but never the cookie list — every export file had an empty "cookies" array, so importing it on another site reported "0 cookies imported". With WP_DEBUG_DISPLAY on, a PHP warning also broke the export response. Exports made with 2.15.1 or earlier carry no cookies: export again after updating.
* Fixed: importing now updates cookies the site already has instead of adding them a second time, and reports how many were added and updated.
* Fixed: export and import carry only the portable settings. Before, internal values were exported raw and importing them could break the Dashboard tab.
* Fixed: on a multisite consent group, the Dashboard's cookie count and the legacy upgrade routines used the wrong table.

= 2.15.1 =
* Fixed: a long button label (for example "Postavke kolačića") spilled out of its button. The banner is a little wider (480 px), buttons keep equal widths, and when the labels still cannot share one row all buttons stack at full width, so "Accept" never outweighs "Reject". On phones they were already stacked.
* Fixed: the "square" corner style in the live preview did not reach the settings-panel excerpt (panel box, save button). The preview now uses the frontend's own selector list.
* The live preview shows the banner at its real width.

= 2.15.0 =
* New: live preview on the Banner & Design tab. The real banner and a piece of the settings panel are rendered on the settings page with the real stylesheet and update as you type, so colours, hover colours, corner style, button weight and hover animation can be judged before saving.
* New: "Follow the visitor's browser language" (General tab, off by default). On a single-language site the banner switches to the visitor's browser language when the plugin has it built in (Croatian, English). Decided in the browser, so it is safe behind page caches. Ignored when WPML, Polylang or TranslatePress runs the site. Only wording the plugin knows is switched; your own texts are shown as entered.
* Changed: the Privacy Policy generator now lives on the Tools tab (the old tab link still opens it), and the Consent Log no longer repeats the Dashboard statistics above the table.

= 2.14.0 =
* New: one consent for a multisite with subdomains (Network tab, super admins only, off by default). One primary site holds the banner, the declaration, the scanner and the consent records; the consent cookie is set for the parent domain; the banner names every website it covers.
* Fixed: database upgrade errors from TEXT columns declared with a default value.

= 2.13.1 =
* Fixed: the plugin could not see its own updates on any site — a manifest without a package hash aborted the update check. Hashes are now verified when the manifest provides one and ignored otherwise; a wrong hash is still blocked.

= 2.13.0 =
* Changed: the frontend stylesheet is delivered non-blocking and the banner waits for it (at most one second) before showing, so it never appears unstyled and no longer holds up the page's first render.

= 2.12.0 =
* Fixed: the banner and settings titles are no longer heading elements, which broke the host page's heading order.
* The dialogs are now named by their visible title instead of a generic label.
* New heading_level attribute on the [r2cc_cookie_table] shortcode.

= 2.11.0 =
* Fixed: banner texts could never be translated — WPML String Translation entries were never applied.
* Translations now work on every language, including English, and for any number of languages.

= 2.10.0 =
* Added: scripts and embeds are now two separate switches — "Auto-block Scripts" and "Auto-block Embeds (iframes)". Third-party scripts can stay blocked while an embed your theme drives with its own JavaScript (a video behind a custom play button, an interactive map) is left untouched, so it keeps working normally. Existing sites are unaffected: with the old single toggle on, embeds keep being blocked until you turn them off yourself. Turning embeds off means they load, and can set cookies, before consent — the setting says so.
* Fixed: the plugin could never update itself and had no "Check for updates" link on the Plugins page. The updater was only registered when signed-update metadata was configured, and since signature checking defaulted to required with no key set, that condition was never true — so no update, and no security fix, could ever reach a site. The updater is now always registered; signature verification is enforced when a public key is configured, and downloads stay locked to the trusted host either way.

= 2.9.0 =
* Added: "Re-init after consent" (General tab). With Auto-block on, a theme that drives an embed with its own JavaScript — a video behind a custom play button, a map — starts up while that content is still blocked, so its button stays dead until the page is reloaded, even though the content itself is restored the instant the visitor accepts. Name your theme's init function here and it is called right after the content comes back, so everything works without a reload.
* Added: "Reload page after consent" — a universal fallback for themes with no init function to call.
* Both are OPT-IN and empty/off by default: a site that configures neither behaves exactly as before. Callback names are reduced server-side to plain identifiers, and each one is called inside its own try/catch, so a missing, renamed or broken function can never break the consent flow.

= 2.8.0 =
* Fixed: with "Auto-block Scripts & Iframes" on, embeds that a theme controls with its own JS (a YouTube video behind a custom play button, for example) never played — permanently, even after accepting cookies and on later visits. The blocker replaced the <iframe> with a placeholder element, so by the time the theme initialised its iframe did not exist and its play button was never wired up. The iframe now stays in the page: only its src is taken away until consent, so the theme finds and binds to the very element it expects, and accepting cookies simply gives the src back.
* Fixed: lazy-loading attributes (data-src and the WP Rocket / LiteSpeed / a3 equivalents) were mangled by the blocker, breaking lazy-loaded embeds. They are now preserved and, while consent is missing, safely parked so no lazy loader can load the embed early — and an embed whose URL lives ONLY in such an attribute is now blocked too, which it previously was not.
* Fixed: a ">" inside an attribute value no longer breaks the rewritten iframe tag, self-closing iframes and fallback content are handled, and the default placeholder height is now valid CSS (it was unitless and silently ignored).
* Added: a `r2cc:iframes-restored` DOM event so a theme or plugin can re-initialise anything that depends on an embed the moment consent restores it.

= 2.7.0 =
* Added: heading & toggle colors (Banner & Design → Colors) so a light-background banner stays readable. New "Banner Title" and — separately — "Settings Title" color pickers (the settings title also recolors the category names), plus a "Settings Toggle (inactive)" color for the OFF switch track. Previously the titles and the inactive switches were hardcoded white/translucent-white and vanished on a light background. Leave the toggle color empty for Automatic — it adapts to your background (dark track on a light panel, light track on a dark panel) so switches never disappear; active switches use your Accept Button color. Defaults reproduce the previous look, so existing sites are unchanged.
* Added: "Reopen Settings Trigger" option (General tab) — choose how visitors reopen the cookie settings after they've made a choice. "Floating button" (default) keeps the built-in floating cookie icon; "Custom link / element" hides that icon and instead lets any element on your site open the settings modal when it carries a configurable CSS class (default `r2cc-reopen-custom-link`, editable). Put that class on a footer link — e.g. `<a href="#" class="r2cc-reopen-custom-link">Cookie Settings</a>` — and it opens the preferences panel. No duplicate IDs: the custom trigger is bound by class, never by the floating button's id. Backward compatible — existing sites keep the floating button until they switch.

= 2.6.0 =
* Added: "Show Delay (ms)" option (General tab) — delays the first appearance of the cookie banner by up to 60 seconds so a site intro animation can finish before the notice shows. Applies only to the first appearance for new visitors; reopening the settings is never delayed. Purely visual: blocked scripts stay blocked during the delay, so no tracking runs before consent.

= 2.5.0 =
* Added: per-button hover colors (Accept/Reject/Settings, background + text) under Banner & Design → Button Hover. Empty = Automatic: light backgrounds darken slightly on hover, dark ones lighten, text color never changes — fixes e.g. a white button with black text becoming unreadable on hover (the old CSS hardcoded a dark-theme overlay and an indigo shadow).
* Added: Hover Animation selector — Color change (default), Text slide + color change, Text slide only (background unchanged), or None. The text slide is a Wandia-style label roll (512ms) and respects the visitor's reduced-motion preference.
* Added: Button Font Weight setting (Inherit from theme / 400 / 500 / 600 / 700; default 600 = previous behavior).
* Changed: outline-button borders and the accept-button hover shadow are now derived from your actual colors instead of hardcoded dark-theme values.

= 2.4.1 =
* Fixed: Fatal TypeError on the front-end when WPML is freshly activated (setup wizard not completed). WPML defines ICL_LANGUAGE_CODE as NULL in that state; the language detector passed it through to string-typed translation helpers and crashed the page. Language detection now validates every multilingual plugin's answer (WPML null/'all', Polylang false, TranslatePress empty) and always falls back to the WordPress locale, guaranteeing a valid language code.

= 2.4.0 =
* Added: "Pages to scan" and "Per-page wait (ms)" controls in the Scanner tab so the headless Deep Scan can be tuned to your host's PHP timeout. The Deep Scan runs as one synchronous request (~10–25s per page); some shared hosts cap PHP/web-server execution at 30–60s, so a 6-page scan could outlast that and fail with a gateway timeout ("Deep scan failed: error") even though the scan itself completed. Fewer pages keeps the round-trip inside the host's window.
* Changed: Default Deep Scan page count lowered from 6 to 3 (clamped server-side to 1–10 pages / 500–10000 ms). Applies to both the manual Deep Scan and scheduled scans.

= 2.3.1 =
* Changed: Renamed the admin menu item to "R2 Cookie Consent" for easier identification in the wp-admin sidebar (no functional change).

= 2.2.0 =
* Added: Corner Style option in Banner & Design tab — choose between Rounded (default, current look) and Square (hard 0-radius) for the banner box, banner/settings-footer buttons, settings panel, and category cards.
* Added: Croatian translations for the new Corner Style UI (Stil kutova / Zaobljeni / Ravni + descriptions).
* Changed: Existing installs keep their current rounded look on upgrade — the new option defaults to "rounded" so nothing visually changes until an admin flips it.

= 2.1.3 =
* Added: Croatian (HR) translations for 60+ known cookie descriptions — descriptions now appear in the site's language (HR) instead of always showing English.
* Added: R2CC_I18n::translate_cookie_description() helper with wildcard pattern matching (_ga_*, _hj*, etc.).
* Changed: Frontend cookie table and admin cookie list now render translated descriptions at display-time. DB stays English as source of truth, so admin edits are preserved and easy to export/import across sites.

= 2.1.2 =
* Fixed: GTM container detection now auto-includes GA4 + Google Ads cookies.
* Fixed: Browser-based scan now uses one-time token to bypass auto-blocker so GTM/GA/Pixel tags can actually set cookies inside the scan iframe.
* Added: Extended known cookies DB with _gcl_*, FPGCLAW, FPGCLDC, FPGCLGB, FPID, FPLC, AEC.

= 2.1.1 =
* Security: Scanner bypass (?r2cc_scan) now requires one-time token.
* Security: Explicit sslverify on scanner HTTP requests.
* Security: Scanner API key excluded from settings export.
* Fixed: wp_unslash() on $_SERVER values in consent logger.

= 2.1.0 =
* New: Headless Browser Cookie Scanner — deep scan via R2 Cookie Scanner service.
* New: Reset All Cookies button.
* Fixed: Scanner false positives (exact plugin slug matching, script-only regex).

= 2.0.11 =
* Changed: Check for updates link moved to action links row (next to Deactivate).
* Added: Success notice after manual update check.

= 2.0.10 =
* Updated PHP requirement to 8.0
* Updated WordPress requirement to 6.0
* Modernized strpos()/substr() calls to PHP 8.0 string functions
* Added PHP/WordPress version gate on activation
* Fixed unescaped admin_url() in action links
* Used wp_date() instead of date() for timezone consistency

= 2.0.7 =
* Fixed: Accept All / Reject All in settings modal now visibly flips toggles with a pulse animation before the modal fades out (900ms delay + fade transition)
* Fixed: Duplicate cookie entries in database from repeated scans — auto-deduplicated on upgrade
* Improved: Settings modal closes with a smooth scale-down + fade-out animation instead of instant disappear

= 2.0.6 =
* Fixed: Cookie settings modal toggles now reflect saved consent state when reopened via floating cookie button
* Fixed: Chart.js race condition — declared as WP script dependency with fallback dynamic loader

= 2.0.5 =
* Fixed: "Accept Necessary" label stuck in database now force-updated to "Reject All" on upgrade
* Fixed: Modal toggle sync visible — Accept/Reject All from modal now shows toggles flipping before closing (600ms delay)
* Fixed: Browser scan PHP critical error — known cookies DB format mismatch resolved
* Fixed: Chart.js now properly enqueued via WordPress on dashboard tab (was failing to load from CDN)
* Fixed: Cache automatically purged on plugin upgrade (WP Rocket, W3TC, LiteSpeed, WP Super Cache)
* Improved: Known cookies database expanded to 50+ patterns (GA, FB, Hotjar, HubSpot, LinkedIn, TikTok, Bing, Matomo, etc.)
* Improved: Chart.js fallback with error message if CDN blocked

= 2.0.4 =
* New: Hybrid cookie scanner — browser-based detection via hidden iframe catches real cookies
* Fixed: Form plugins (CF7, WPForms, Gravity Forms) now detected individually with correct cookies
* Changed: Scanner results table shows Source column (Server/Browser)
* Unknown cookies from browser scan flagged for review in Cookies tab

= 2.0.3 =
* Fixed: Admin-only cookies (wordpress_logged_in, wp-settings, comment_author, yoast-seo, etc.) now automatically purged from cookie database on update
* Added: Version-based upgrade routine — runs cleanup once per version bump

= 2.0.2 =
* Fixed: Accept All / Reject All now visually sync toggle switches in settings panel
* Fixed: Duplicate identical consent actions no longer logged to database
* Consent cookie is always updated, but server log is skipped when categories and type are unchanged

= 2.0.1 =
* Fixed: Settings tabs no longer reset colors when saving other tabs
* Fixed: Modal scrollbar styling for dark theme
* Fixed: Category expand/collapse clipping in settings panel
* Changed: Button order — Cookie Settings, Reject All, Accept All
* Changed: Removed admin-only cookies from scanner (wordpress_logged_in, wp-settings, yoast-seo, etc.)
* Added: Plugin's own consent cookies (r2cc_consent, r2cc_consent_uid) auto-detected by scanner

= 2.0.0 =
* Auto script & iframe blocker (60+ services)
* Consent analytics dashboard
* Shortcodes: settings button, cookie table, consent status
* Consent version bump (force re-consent)
* Import/Export settings JSON
* Scheduled cookie re-scan via WP-Cron
* Email notifications for new cookies
* Cache-compatible GCM

= 1.0.0 =
* Initial release
