| 1 |
<?php |
| 2 |
/** |
| 3 |
* The catalog's blocking-pattern buckets. |
| 4 |
* |
| 5 |
* A service's patterns are grouped by the tag family the resource arrives on, |
| 6 |
* because that - not the domain - decides whether the blocker can act on it. |
| 7 |
* Every consumer of the blocking view reads this table instead of naming |
| 8 |
* buckets itself; hard-coding `scripts`/`iframes` in each of them is what let a |
| 9 |
* stylesheet host be filed as a script and reported as blocked. |
| 10 |
* |
| 11 |
* @package SureCookie\Inc\Modules\Services |
| 12 |
* @since 1.5.0 |
| 13 |
*/ |
| 14 |
|
| 15 |
namespace SureCookie\Inc\Modules\Services; |
| 16 |
|
| 17 |
if ( ! defined( 'ABSPATH' ) ) { |
| 18 |
exit; // Exit if accessed directly. |
| 19 |
} |
| 20 |
|
| 21 |
/** |
| 22 |
* Pattern_Kinds |
| 23 |
* |
| 24 |
* @since 1.5.0 |
| 25 |
*/ |
| 26 |
final class Pattern_Kinds { |
| 27 |
/** |
| 28 |
* Bucket => { resource, enforced }. |
| 29 |
* |
| 30 |
* `enforced` mirrors the passes in `Blocker`, which rewrites `<script>`, |
| 31 |
* `<iframe>`, `<embed>`, `<object>` and `<link>` and nothing else, and the |
| 32 |
* DOM guard, which wraps the same element types. `media` has no planned |
| 33 |
* pass: an `<img>` is not a tag any pass can rewrite, and the image CDNs |
| 34 |
* that live there are essential, so they are never gated anyway. |
| 35 |
* `BlockingSurfaceTest` fails if this drifts from the passes. |
| 36 |
* |
| 37 |
* `styles` covers the whole stylesheet delivery chain: the |
| 38 |
* `<link rel=stylesheet>` itself and the font or asset files that |
| 39 |
* stylesheet then fetches. Both are gated by the link pass - the second |
| 40 |
* because a theme that preloads a font hits it directly, and because with |
| 41 |
* the stylesheet parked nothing ever reads the `@font-face`. |
| 42 |
* |
| 43 |
* `resource` is the kind a scan row for such a pattern is stored under: |
| 44 |
* every scanner files a non-frame resource as a script, so a stylesheet or |
| 45 |
* image host arrives on the `scripts` side of the scanned-resources option. |
| 46 |
*/ |
| 47 |
private const KINDS = [ |
| 48 |
'scripts' => [ |
| 49 |
'resource' => 'script', |
| 50 |
'enforced' => true, |
| 51 |
], |
| 52 |
'iframes' => [ |
| 53 |
'resource' => 'iframe', |
| 54 |
'enforced' => true, |
| 55 |
], |
| 56 |
'styles' => [ |
| 57 |
'resource' => 'script', |
| 58 |
'enforced' => true, |
| 59 |
], |
| 60 |
'media' => [ |
| 61 |
'resource' => 'script', |
| 62 |
'enforced' => false, |
| 63 |
], |
| 64 |
]; |
| 65 |
|
| 66 |
/** |
| 67 |
* Every bucket name, in catalog order. |
| 68 |
* |
| 69 |
* @since 1.5.0 |
| 70 |
* @return array<int, string> |
| 71 |
*/ |
| 72 |
public static function buckets(): array { |
| 73 |
return array_keys( self::KINDS ); |
| 74 |
} |
| 75 |
|
| 76 |
/** |
| 77 |
* Buckets a blocking pass can act on, as bucket => resource kind. |
| 78 |
* |
| 79 |
* @since 1.5.0 |
| 80 |
* @return array<string, string> |
| 81 |
*/ |
| 82 |
public static function enforced(): array { |
| 83 |
return self::filter_by_enforcement( true ); |
| 84 |
} |
| 85 |
|
| 86 |
/** |
| 87 |
* Buckets no blocking pass reaches, as bucket => resource kind. |
| 88 |
* |
| 89 |
* @since 1.5.0 |
| 90 |
* @return array<string, string> |
| 91 |
*/ |
| 92 |
public static function unenforced(): array { |
| 93 |
return self::filter_by_enforcement( false ); |
| 94 |
} |
| 95 |
|
| 96 |
/** |
| 97 |
* Whether a blocking pass acts on this bucket. |
| 98 |
* |
| 99 |
* @since 1.5.0 |
| 100 |
* @param string $bucket Bucket name. |
| 101 |
* @return bool |
| 102 |
*/ |
| 103 |
public static function is_enforced( string $bucket ): bool { |
| 104 |
return ! empty( self::KINDS[ $bucket ]['enforced'] ); |
| 105 |
} |
| 106 |
|
| 107 |
/** |
| 108 |
* The resource kind ('script'|'iframe') a bucket's rows are stored under. |
| 109 |
* |
| 110 |
* @since 1.5.0 |
| 111 |
* @param string $bucket Bucket name. |
| 112 |
* @return string Resource kind, or '' for an unknown bucket. |
| 113 |
*/ |
| 114 |
public static function resource_kind( string $bucket ): string { |
| 115 |
return (string) ( self::KINDS[ $bucket ]['resource'] ?? '' ); |
| 116 |
} |
| 117 |
|
| 118 |
/** |
| 119 |
* Buckets whose enforcement matches, as bucket => resource kind. |
| 120 |
* |
| 121 |
* @since 1.5.0 |
| 122 |
* @param bool $enforced Enforcement to select. |
| 123 |
* @return array<string, string> |
| 124 |
*/ |
| 125 |
private static function filter_by_enforcement( bool $enforced ): array { |
| 126 |
$buckets = []; |
| 127 |
|
| 128 |
foreach ( self::KINDS as $bucket => $meta ) { |
| 129 |
if ( $meta['enforced'] === $enforced ) { |
| 130 |
$buckets[ $bucket ] = $meta['resource']; |
| 131 |
} |
| 132 |
} |
| 133 |
|
| 134 |
return $buckets; |
| 135 |
} |
| 136 |
} |
| 137 |
|