Repository navigation
Add generic script/iframe blocking until consent - #10
Closed
Castellon-ACM wants to merge 1 commit into
Closed
Castellon-ACM wants to merge 1 commit into
Castellon-ACM wants to merge 1 commit into
Conversation
Introduce ScriptBlocker, a new Frontend module that holds back arbitrary third-party <script src="..."> and <iframe src="..."> embeds (YouTube, Maps, social widgets, custom tracking snippets) until the visitor accepts the configured consent category — complementing CookieNotice's existing Google Consent Mode v2 support, which only affects Consent-Mode-aware tags. Server-side: a new "Script & Iframe Blocking" settings field stores a list of match-pattern/category rules (one-rule-per-line "pattern|category" textarea, a simple v1 given no existing repeatable-rows UI to match). When at least one rule is configured, ScriptBlocker buffers the frontend HTML (ob_start(), skipped entirely for admin/AJAX/REST/cron requests, and never started at all when no rules exist) and rewrites matching opening tags to inert placeholders via conservative, well-anchored regexes: <script> gets type="text/plain" with its src moved to data-frcn-src, and <iframe> gets its src swapped to "about:blank" with the real URL likewise moved to data-frcn-src. Client-side: a new frontconsent-script-blocker.js file (enqueued only when rules are configured) revives placeholders once their category is allowed — creating a brand-new <script> element (cloning a text/plain script and changing its type would not make it execute) or restoring an <iframe>'s real src — hooking into the same frcnCookieConsent event frontconsent-cookie-notice.js already dispatches. Test coverage: tests/Unit/ScriptBlockerTest.php covers matching/non-matching scripts and iframes, self-closing tags, attribute order/quoting variations, multiple tags on one page, case-insensitivity, and a data-src lazy-load attribute correctly not being confused with the real src (a real bug found and fixed during development, alongside a case-sensitivity bug in the buffering short-circuit). tests/js/script-blocker.test.js covers script revival, iframe src restoration, a rejected category being left alone, and a per-category override extension point. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #5.
includes/Frontend/ScriptBlocker.php, a new Frontend module that holds back arbitrary third-party<script src="...">and<iframe src="...">embeds (YouTube, Maps, social widgets, custom tracking snippets, etc.) until the visitor accepts the configured consent category — a generic complement toCookieNotice's existing Google Consent Mode v2 signaling, which only affects Consent-Mode-aware tags.pattern|categorytextarea (a deliberately simple v1 — no existing repeatable-rows UI on this settings screen to match the style of).categoryreuses the existinganalytics/marketingslugsCookieNoticealready uses for tracking integrations, rather than inventing new ones.assets/cookie-notice/frontconsent-script-blocker.js, enqueued only when at least one rule is configured, which revives placeholders once their category is allowed, hooking into the samefrcnCookieConsenteventfrontconsent-cookie-notice.jsalready dispatches.Safety/performance considerations
ob_start()is never called, and the revival script is never enqueued, when no rules are configured — a site that doesn't use this feature pays nothing extra.maybe_start_output_buffer()explicitly bails onis_admin(),wp_doing_ajax(),wp_doing_cron(),wp_is_json_request(),REST_REQUESTandXMLRPC_REQUEST.>even when a quoted attribute value contains one, attribute extraction/removal handles both quote styles, unquoted values, and irregular whitespace, and self-closing tags keep their trailing/at the end of the tag rather than in the middle of the newly appended attributes.strpos($html, 'src'), silently skipping rewriting for any page whose markup happened to use uppercase tags/attributes (<SCRIPT SRC="...">). Fixed tostripos().\bbefore the attribute name, which also matches right after a hyphen — so a real-worlddata-src="..."lazy-load attribute (used by lazysizes and similar libraries) was wrongly matched as the actualsrcattribute. Fixed with a(?<![\w-])negative lookbehind instead.src: a blocked<script>has itssrcrenamed todata-frcn-srcand getstype="text/plain"(per the HTML spec, an unsupported script type is never fetched); a blocked<iframe>getssrc="about:blank"rather than nosrcat all (which some browsers treat as reloading the parent document).<script>element rather than mutating the placeholder in place — cloning atype="text/plain"script and changing its type does not make a browser execute it, per the HTML spec's "already started" flag.Test plan
composer lint(phpcs) — passes.composer phpstan— passes, no errors.tests/Unit/ScriptBlockerTest.php(script/iframe matching, non-matching, self-closing tags, attribute order/quoting variations, multiple tags per page, case-insensitivity,data-srcregression, no-rules no-op, textarea round-trip) — logic additionally verified via a standalone PHP script exercising the same class outside the WordPress test bootstrap, since a WordPress+MySQL integration test environment could not be provisioned in this sandbox (nomysql/svnavailable, and installing them via Homebrew required building LLVM from source). The PHP syntax is valid (php -l) and bothphpcs/phpstanpass over the new file.tests/js/script-blocker.test.js, added topackage.json'stest:script-blockerscript (and covered by the existingtest:jsglob) —npm run test:jspasses all 26 tests (6 new + 20 pre-existing), confirming no regressions.Caveats
composer test) could not be executed end-to-end in this environment due to the missing WordPress+MySQL test fixture — please run it in CI/locally to confirm; lint, phpstan, and the standalone functional check give reasonable confidence in the meantime.🤖 Generated with Claude Code