From 816ccb4292381a9e425dcfb3bdc19f5c2c7a0136 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 22 Apr 2026 21:41:50 +0200 Subject: [PATCH 1/3] Unified Snippets: implement Tier 2 DB scanners --- src/php/Plugin.php | 6 + .../Adapters/DB_Scanner_Adapter.php | 147 +++++++++++++++ .../Header_Footer_Code_Manager_Scanner.php | 73 ++++++++ .../Insert_Headers_And_Footers_Scanner.php | 81 ++++++++ .../Insert_PHP_Code_Snippet_Scanner.php | 82 +++++++++ tests/phpunit/fakes/Fake_Hfcm_Importer.php | 36 ++++ tests/phpunit/fakes/Fake_Ihaf_Importer.php | 38 ++++ tests/phpunit/fakes/Fake_Ipcs_Importer.php | 36 ++++ tests/phpunit/test-db-scanners.php | 173 ++++++++++++++++++ 9 files changed, 672 insertions(+) create mode 100644 src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php create mode 100644 src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php create mode 100644 src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php create mode 100644 src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php create mode 100644 tests/phpunit/fakes/Fake_Hfcm_Importer.php create mode 100644 tests/phpunit/fakes/Fake_Ihaf_Importer.php create mode 100644 tests/phpunit/fakes/Fake_Ipcs_Importer.php create mode 100644 tests/phpunit/test-db-scanners.php diff --git a/src/php/Plugin.php b/src/php/Plugin.php index 35fce33a..b8fd5ac5 100644 --- a/src/php/Plugin.php +++ b/src/php/Plugin.php @@ -23,7 +23,10 @@ use Code_Snippets\UnifiedSnippets\REST\Scan_REST_Controller; use Code_Snippets\UnifiedSnippets\Scanners\Additional_CSS_Scanner; use Code_Snippets\UnifiedSnippets\Scanners\Functions_Php_Scanner; +use Code_Snippets\UnifiedSnippets\Scanners\Header_Footer_Code_Manager_Scanner; use Code_Snippets\UnifiedSnippets\Scanners\Htaccess_Scanner; +use Code_Snippets\UnifiedSnippets\Scanners\Insert_Headers_And_Footers_Scanner; +use Code_Snippets\UnifiedSnippets\Scanners\Insert_PHP_Code_Snippet_Scanner; use Code_Snippets\UnifiedSnippets\Scanners\Mu_Plugins_Scanner; use Code_Snippets\UnifiedSnippets\Scanners\Wp_Config_Scanner; @@ -179,6 +182,9 @@ private function init_unified_snippets(): void { $this->unified_snippets->register( new Htaccess_Scanner() ); $this->unified_snippets->register( new Wp_Config_Scanner() ); $this->unified_snippets->register( new Mu_Plugins_Scanner() ); + $this->unified_snippets->register( new Insert_Headers_And_Footers_Scanner() ); + $this->unified_snippets->register( new Header_Footer_Code_Manager_Scanner() ); + $this->unified_snippets->register( new Insert_PHP_Code_Snippet_Scanner() ); new Scan_REST_Controller( $this->unified_snippets, $this->unified_snippets_store ); } diff --git a/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php b/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php new file mode 100644 index 00000000..dc7c51c3 --- /dev/null +++ b/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php @@ -0,0 +1,147 @@ +importer = $importer ?? $this->create_importer(); + } + + /** + * Create the default importer instance for this adapter. + * + * @return Plugin_Importer + */ + abstract protected function create_importer(): Plugin_Importer; + + /** + * Name of the source plugin's database table, used to build synthetic URIs. + * + * @return string e.g. 'hfcm_scripts'. + */ + abstract protected function get_table_name(): string; + + /** + * Map a single raw row from the importer into Discovered_Snippet field overrides. + * + * Return null to skip the row (unsupported type, missing code, etc.). + * + * @param array $row Raw row cast to associative array. + * + * @return array|null + */ + abstract protected function map_row( array $row ): ?array; + + /** + * {@inheritDoc} + */ + public function is_available(): bool { + return (bool) call_user_func( [ get_class( $this->importer ), 'is_active' ] ); + } + + /** + * {@inheritDoc} + */ + public function scan(): array { + if ( ! $this->is_available() ) { + return []; + } + + $snippets = []; + + foreach ( $this->importer->get_data() as $row ) { + $row_array = is_array( $row ) ? $row : (array) $row; + $fields = $this->map_row( $row_array ); + + if ( null === $fields ) { + continue; + } + + $snippets[] = $this->build_snippet( $this->with_defaults( $fields ) ); + } + + return $snippets; + } + + /** + * Fill in common Discovered_Snippet fields that are shared by all DB-backed adapters. + * + * @param array $fields Adapter-supplied field overrides. + * + * @return array + */ + private function with_defaults( array $fields ): array { + return array_merge( + [ + 'source_type' => 'plugin', + 'source_name' => $this->get_label(), + 'line_start' => 0, + 'line_end' => 0, + ], + $fields + ); + } + + /** + * Build a synthetic URI identifying a row in the source plugin's table. + * + * @param int|string $id Row identifier. + * + * @return string e.g. 'db://hfcm_scripts/42'. + */ + protected function build_source_path( $id ): string { + return 'db://' . $this->get_table_name() . '/' . $id; + } + + /** + * Derive a {@see Discovered_Snippet} `type` from the source plugin's code-type value. + * + * Unknown or empty types fall back to 'php' to satisfy the model's allow-list. + * + * @param string $code_type Source-plugin code type, e.g. 'html', 'universal'. + * + * @return string + */ + protected function derive_type( string $code_type ): string { + switch ( strtolower( $code_type ) ) { + case 'css': + return 'css'; + case 'js': + case 'javascript': + return 'js'; + case 'html': + case 'universal': + return 'html'; + case 'php': + case '': + return 'php'; + default: + return 'mixed'; + } + } +} diff --git a/src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php b/src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php new file mode 100644 index 00000000..1ff1427f --- /dev/null +++ b/src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php @@ -0,0 +1,73 @@ + $row Row returned by the importer. + * + * @return array|null Null when the row should be skipped. + */ + protected function map_row( array $row ): ?array { + $code = (string) ( $row['snippet'] ?? '' ); + + if ( '' === trim( $code ) ) { + return null; + } + + $id = (int) ( $row['script_id'] ?? 0 ); + $title = (string) ( $row['name'] ?? '' ); + + return [ + 'name' => '' !== $title ? $title : sprintf( 'HFCM #%d', $id ), + 'code' => $code, + 'type' => $this->derive_type( (string) ( $row['snippet_type'] ?? '' ) ), + 'source_path' => $this->build_source_path( $id ), + 'is_active' => 'active' === ( $row['status'] ?? '' ), + ]; + } +} diff --git a/src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php b/src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php new file mode 100644 index 00000000..bb1aef16 --- /dev/null +++ b/src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php @@ -0,0 +1,81 @@ + $row Row returned by the importer. + * + * @return array|null Null when the row should be skipped. + */ + protected function map_row( array $row ): ?array { + $code_type = (string) ( $row['code_type'] ?? '' ); + + if ( ! in_array( $code_type, self::SUPPORTED_CODE_TYPES, true ) ) { + return null; + } + + $code = (string) ( $row['code'] ?? '' ); + + if ( '' === trim( $code ) ) { + return null; + } + + $id = $row['table_data']['id'] ?? ( $row['id'] ?? 0 ); + $title = (string) ( $row['table_data']['title'] ?? ( $row['title'] ?? '' ) ); + + return [ + 'name' => '' !== $title ? $title : sprintf( 'WPCode #%d', (int) $id ), + 'code' => $code, + 'type' => $this->derive_type( $code_type ), + 'source_path' => $this->build_source_path( (int) $id ), + 'is_active' => true, + ]; + } +} diff --git a/src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php b/src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php new file mode 100644 index 00000000..296be719 --- /dev/null +++ b/src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php @@ -0,0 +1,82 @@ + $row Row returned by the importer. + * + * @return array|null Null when the row should be skipped. + */ + protected function map_row( array $row ): ?array { + $code = (string) ( $row['content'] ?? '' ); + + if ( '' === trim( $code ) ) { + return null; + } + + $id = (int) ( $row['id'] ?? 0 ); + $title = (string) ( $row['title'] ?? '' ); + + return [ + 'name' => '' !== $title ? $title : sprintf( 'Insert PHP #%d', $id ), + 'code' => $code, + 'type' => 'php', + 'source_path' => $this->build_source_path( $id ), + 'is_active' => 1 === (int) ( $row['status'] ?? 0 ), + ]; + } +} diff --git a/tests/phpunit/fakes/Fake_Hfcm_Importer.php b/tests/phpunit/fakes/Fake_Hfcm_Importer.php new file mode 100644 index 00000000..d22ea3d7 --- /dev/null +++ b/tests/phpunit/fakes/Fake_Hfcm_Importer.php @@ -0,0 +1,36 @@ +> + */ + public array $rows = []; + + /** + * {@inheritDoc} + */ + public static function is_active(): bool { + return true; + } + + /** + * Return the canned rows regardless of the requested IDs. + * + * @param array $ids_to_import Unused. + * + * @return array> + */ + public function get_data( array $ids_to_import = [] ): array { + return $this->rows; + } +} diff --git a/tests/phpunit/fakes/Fake_Ihaf_Importer.php b/tests/phpunit/fakes/Fake_Ihaf_Importer.php new file mode 100644 index 00000000..d9d1b599 --- /dev/null +++ b/tests/phpunit/fakes/Fake_Ihaf_Importer.php @@ -0,0 +1,38 @@ +> + */ + public array $rows = []; + + /** + * {@inheritDoc} + */ + public static function is_active(): bool { + return true; + } + + /** + * Return the canned rows regardless of the requested IDs. + * + * @param array $ids_to_import Unused. + * + * @return array> + */ + public function get_data( array $ids_to_import = [] ): array { + return $this->rows; + } +} diff --git a/tests/phpunit/fakes/Fake_Ipcs_Importer.php b/tests/phpunit/fakes/Fake_Ipcs_Importer.php new file mode 100644 index 00000000..354eebba --- /dev/null +++ b/tests/phpunit/fakes/Fake_Ipcs_Importer.php @@ -0,0 +1,36 @@ + + */ + public array $rows = []; + + /** + * {@inheritDoc} + */ + public static function is_active(): bool { + return true; + } + + /** + * Return the canned rows regardless of the requested IDs. + * + * @param array $ids_to_import Unused. + * + * @return array + */ + public function get_data( array $ids_to_import = [] ): array { + return $this->rows; + } +} diff --git a/tests/phpunit/test-db-scanners.php b/tests/phpunit/test-db-scanners.php new file mode 100644 index 00000000..03ff2e5a --- /dev/null +++ b/tests/phpunit/test-db-scanners.php @@ -0,0 +1,173 @@ +rows = [ + [ + 'code' => "console.log('hi');", + 'code_type' => 'js', + 'table_data' => [ + 'id' => 7, + 'title' => 'Hello JS', + ], + ], + [ + 'code' => '// plain php', + 'code_type' => 'php', + 'table_data' => [ + 'id' => 8, + 'title' => 'PHP Helper', + ], + ], + [ + 'code' => '', + 'code_type' => 'text', + 'table_data' => [ + 'id' => 9, + 'title' => 'Unsupported', + ], + ], + [ + 'code' => ' ', + 'code_type' => 'php', + 'table_data' => [ + 'id' => 10, + 'title' => 'Empty', + ], + ], + ]; + + $scanner = new Insert_Headers_And_Footers_Scanner( $importer ); + + $this->assertTrue( $scanner->is_available() ); + $this->assertSame( 'wpcode', $scanner->get_id() ); + + $results = $scanner->scan(); + $this->assertCount( 2, $results ); + + $first = $results[0]; + $this->assertSame( 'Hello JS', $first->name ); + $this->assertSame( 'js', $first->type ); + $this->assertSame( 'plugin', $first->source_type ); + $this->assertSame( 'db://wpcode_snippets/7', $first->source_path ); + $this->assertTrue( $first->is_active ); + $this->assertSame( 'wpcode', $first->scanner_id ); + + $this->assertSame( 'php', $results[1]->type ); + } + + /** + * HFCM adapter reads `snippet`/`script_id`/`status` and builds a proper URI. + */ + public function test_hfcm_adapter_maps_rows_and_active_flag() { + $importer = new Fake_Hfcm_Importer(); + $importer->rows = [ + [ + 'script_id' => 42, + 'name' => 'Analytics Header', + 'snippet' => '', + 'snippet_type' => 'html', + 'status' => 'active', + ], + [ + 'script_id' => 43, + 'name' => 'Disabled Pixel', + 'snippet' => '', + 'snippet_type' => 'html', + 'status' => 'inactive', + ], + [ + 'script_id' => 44, + 'name' => 'Empty', + 'snippet' => '', + 'snippet_type' => 'html', + 'status' => 'active', + ], + ]; + + $scanner = new Header_Footer_Code_Manager_Scanner( $importer ); + $results = $scanner->scan(); + + $this->assertCount( 2, $results ); + + $active = $results[0]; + $this->assertSame( 'Analytics Header', $active->name ); + $this->assertSame( 'html', $active->type ); + $this->assertSame( 'db://hfcm_scripts/42', $active->source_path ); + $this->assertTrue( $active->is_active ); + $this->assertSame( 'hfcm', $active->scanner_id ); + + $this->assertFalse( $results[1]->is_active ); + } + + /** + * IPCS adapter treats status=1 as active and always emits PHP snippets. + */ + public function test_ipcs_adapter_maps_rows_and_risk() { + $importer = new Fake_Ipcs_Importer(); + $importer->rows = [ + (object) [ + 'id' => 5, + 'title' => 'Custom Hook', + 'content' => " 1, + ], + (object) [ + 'id' => 6, + 'title' => '', + 'content' => ' 0, + ], + ]; + + $scanner = new Insert_PHP_Code_Snippet_Scanner( $importer ); + + $this->assertSame( 'high', $scanner->get_risk_level() ); + + $results = $scanner->scan(); + $this->assertCount( 2, $results ); + + $this->assertSame( 'Custom Hook', $results[0]->name ); + $this->assertSame( 'php', $results[0]->type ); + $this->assertSame( 'db://xyz_ips_short_code/5', $results[0]->source_path ); + $this->assertTrue( $results[0]->is_active ); + $this->assertSame( 'high', $results[0]->risk_level ); + + $this->assertSame( 'Insert PHP #6', $results[1]->name ); + $this->assertFalse( $results[1]->is_active ); + } + + /** + * Adapters short-circuit when the source plugin is inactive. + */ + public function test_adapter_returns_empty_when_unavailable() { + if ( ! function_exists( 'is_plugin_active' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $hfcm = new Header_Footer_Code_Manager_Scanner(); + + $this->assertFalse( $hfcm->is_available() ); + $this->assertSame( [], $hfcm->scan() ); + } +} From 68372bf971371f102dfac95fa835a0c06b6b169b Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 22 Apr 2026 21:54:46 +0200 Subject: [PATCH 2/3] Unified Snippets: implement Tier 2 DB scanners --- .../Adapters/DB_Scanner_Adapter.php | 20 --- .../Header_Footer_Code_Manager_Scanner.php | 19 --- .../Insert_Headers_And_Footers_Scanner.php | 19 --- .../Insert_PHP_Code_Snippet_Scanner.php | 24 ---- tests/phpunit/test-db-scanners.php | 133 ++++++------------ 5 files changed, 44 insertions(+), 171 deletions(-) diff --git a/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php b/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php index dc7c51c3..3f4b7160 100644 --- a/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php +++ b/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php @@ -23,11 +23,6 @@ abstract class DB_Scanner_Adapter extends Scanner_Base { */ protected Plugin_Importer $importer; - /** - * Class constructor. - * - * @param Plugin_Importer|null $importer Optional importer override, useful for tests. - */ public function __construct( ?Plugin_Importer $importer = null ) { $this->importer = $importer ?? $this->create_importer(); } @@ -57,16 +52,10 @@ abstract protected function get_table_name(): string; */ abstract protected function map_row( array $row ): ?array; - /** - * {@inheritDoc} - */ public function is_available(): bool { return (bool) call_user_func( [ get_class( $this->importer ), 'is_active' ] ); } - /** - * {@inheritDoc} - */ public function scan(): array { if ( ! $this->is_available() ) { return []; @@ -88,13 +77,6 @@ public function scan(): array { return $snippets; } - /** - * Fill in common Discovered_Snippet fields that are shared by all DB-backed adapters. - * - * @param array $fields Adapter-supplied field overrides. - * - * @return array - */ private function with_defaults( array $fields ): array { return array_merge( [ @@ -121,8 +103,6 @@ protected function build_source_path( $id ): string { /** * Derive a {@see Discovered_Snippet} `type` from the source plugin's code-type value. * - * Unknown or empty types fall back to 'php' to satisfy the model's allow-list. - * * @param string $code_type Source-plugin code type, e.g. 'html', 'universal'. * * @return string diff --git a/src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php b/src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php index 1ff1427f..d42b9998 100644 --- a/src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php +++ b/src/php/UnifiedSnippets/Scanners/Header_Footer_Code_Manager_Scanner.php @@ -17,41 +17,22 @@ */ class Header_Footer_Code_Manager_Scanner extends DB_Scanner_Adapter { - /** - * {@inheritDoc} - */ public function get_id(): string { return 'hfcm'; } - /** - * {@inheritDoc} - */ public function get_label(): string { return __( 'Header Footer Code Manager', 'code-snippets' ); } - /** - * {@inheritDoc} - */ protected function create_importer(): Plugin_Importer { return new Header_Footer_Code_Manager_Plugin_Importer(); } - /** - * {@inheritDoc} - */ protected function get_table_name(): string { return 'hfcm_scripts'; } - /** - * Map an HFCM row into Discovered_Snippet field overrides. - * - * @param array $row Row returned by the importer. - * - * @return array|null Null when the row should be skipped. - */ protected function map_row( array $row ): ?array { $code = (string) ( $row['snippet'] ?? '' ); diff --git a/src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php b/src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php index bb1aef16..652abf4e 100644 --- a/src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php +++ b/src/php/UnifiedSnippets/Scanners/Insert_Headers_And_Footers_Scanner.php @@ -19,41 +19,22 @@ class Insert_Headers_And_Footers_Scanner extends DB_Scanner_Adapter { private const SUPPORTED_CODE_TYPES = [ 'php', 'css', 'js', 'html', 'universal' ]; - /** - * {@inheritDoc} - */ public function get_id(): string { return 'wpcode'; } - /** - * {@inheritDoc} - */ public function get_label(): string { return __( 'WPCode (Insert Headers and Footers)', 'code-snippets' ); } - /** - * {@inheritDoc} - */ protected function create_importer(): Plugin_Importer { return new Insert_Headers_And_Footers_Plugin_Importer(); } - /** - * {@inheritDoc} - */ protected function get_table_name(): string { return 'wpcode_snippets'; } - /** - * Map a WPCode row into Discovered_Snippet field overrides. - * - * @param array $row Row returned by the importer. - * - * @return array|null Null when the row should be skipped. - */ protected function map_row( array $row ): ?array { $code_type = (string) ( $row['code_type'] ?? '' ); diff --git a/src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php b/src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php index 296be719..f47a2d90 100644 --- a/src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php +++ b/src/php/UnifiedSnippets/Scanners/Insert_PHP_Code_Snippet_Scanner.php @@ -17,50 +17,26 @@ */ class Insert_PHP_Code_Snippet_Scanner extends DB_Scanner_Adapter { - /** - * {@inheritDoc} - */ public function get_id(): string { return 'insert-php-code-snippet'; } - /** - * {@inheritDoc} - */ public function get_label(): string { return __( 'Insert PHP Code Snippet', 'code-snippets' ); } - /** - * Snippets from this source execute PHP, so they carry a higher risk than CSS/HTML sources. - * - * @return string - */ public function get_risk_level(): string { return 'high'; } - /** - * {@inheritDoc} - */ protected function create_importer(): Plugin_Importer { return new Insert_PHP_Code_Snippet_Plugin_Importer(); } - /** - * {@inheritDoc} - */ protected function get_table_name(): string { return 'xyz_ips_short_code'; } - /** - * Map an Insert PHP Code Snippet row into Discovered_Snippet field overrides. - * - * @param array $row Row returned by the importer. - * - * @return array|null Null when the row should be skipped. - */ protected function map_row( array $row ): ?array { $code = (string) ( $row['content'] ?? '' ); diff --git a/tests/phpunit/test-db-scanners.php b/tests/phpunit/test-db-scanners.php index 03ff2e5a..8bee1c43 100644 --- a/tests/phpunit/test-db-scanners.php +++ b/tests/phpunit/test-db-scanners.php @@ -10,156 +10,111 @@ require_once __DIR__ . '/fakes/Fake_Hfcm_Importer.php'; require_once __DIR__ . '/fakes/Fake_Ipcs_Importer.php'; -/** - * Tests for the DB-aware scanner adapters. - * - * @group unified-snippets - */ class DB_Scanners_Test extends TestCase { - /** - * WPCode adapter maps supported code types and skips unsupported ones. - */ - public function test_wpcode_adapter_maps_supported_types() { + public function test_wpcode_filters_unsupported_and_empty() { $importer = new Fake_Ihaf_Importer(); $importer->rows = [ [ - 'code' => "console.log('hi');", - 'code_type' => 'js', + 'code' => "console.log('hi');", + 'code_type' => 'js', 'table_data' => [ - 'id' => 7, - 'title' => 'Hello JS', + 'id' => 1, + 'title' => 'JS', ], ], [ - 'code' => '// plain php', - 'code_type' => 'php', + 'code' => '// php', + 'code_type' => 'php', 'table_data' => [ - 'id' => 8, - 'title' => 'PHP Helper', + 'id' => 2, + 'title' => 'PHP', ], ], [ - 'code' => '', - 'code_type' => 'text', + 'code' => '', + 'code_type' => 'text', 'table_data' => [ - 'id' => 9, + 'id' => 3, 'title' => 'Unsupported', ], ], [ - 'code' => ' ', - 'code_type' => 'php', + 'code' => ' ', + 'code_type' => 'php', 'table_data' => [ - 'id' => 10, - 'title' => 'Empty', + 'id' => 4, + 'title' => 'Blank', ], ], ]; - $scanner = new Insert_Headers_And_Footers_Scanner( $importer ); + $results = ( new Insert_Headers_And_Footers_Scanner( $importer ) )->scan(); - $this->assertTrue( $scanner->is_available() ); - $this->assertSame( 'wpcode', $scanner->get_id() ); - - $results = $scanner->scan(); $this->assertCount( 2, $results ); - - $first = $results[0]; - $this->assertSame( 'Hello JS', $first->name ); - $this->assertSame( 'js', $first->type ); - $this->assertSame( 'plugin', $first->source_type ); - $this->assertSame( 'db://wpcode_snippets/7', $first->source_path ); - $this->assertTrue( $first->is_active ); - $this->assertSame( 'wpcode', $first->scanner_id ); - + $this->assertSame( 'js', $results[0]->type ); $this->assertSame( 'php', $results[1]->type ); } - /** - * HFCM adapter reads `snippet`/`script_id`/`status` and builds a proper URI. - */ - public function test_hfcm_adapter_maps_rows_and_active_flag() { + public function test_hfcm_active_flag_and_empty_skip() { $importer = new Fake_Hfcm_Importer(); $importer->rows = [ [ - 'script_id' => 42, - 'name' => 'Analytics Header', - 'snippet' => '', + 'script_id' => 1, + 'name' => 'On', + 'snippet' => '1', 'snippet_type' => 'html', - 'status' => 'active', + 'status' => 'active', ], [ - 'script_id' => 43, - 'name' => 'Disabled Pixel', - 'snippet' => '', + 'script_id' => 2, + 'name' => 'Off', + 'snippet' => '2', 'snippet_type' => 'html', - 'status' => 'inactive', + 'status' => 'inactive', ], [ - 'script_id' => 44, - 'name' => 'Empty', - 'snippet' => '', + 'script_id' => 3, + 'name' => 'Blank', + 'snippet' => '', 'snippet_type' => 'html', - 'status' => 'active', + 'status' => 'active', ], ]; - $scanner = new Header_Footer_Code_Manager_Scanner( $importer ); - $results = $scanner->scan(); + $results = ( new Header_Footer_Code_Manager_Scanner( $importer ) )->scan(); $this->assertCount( 2, $results ); - - $active = $results[0]; - $this->assertSame( 'Analytics Header', $active->name ); - $this->assertSame( 'html', $active->type ); - $this->assertSame( 'db://hfcm_scripts/42', $active->source_path ); - $this->assertTrue( $active->is_active ); - $this->assertSame( 'hfcm', $active->scanner_id ); - + $this->assertTrue( $results[0]->is_active ); $this->assertFalse( $results[1]->is_active ); } - /** - * IPCS adapter treats status=1 as active and always emits PHP snippets. - */ - public function test_ipcs_adapter_maps_rows_and_risk() { + public function test_ipcs_active_flag_name_fallback_and_risk() { $importer = new Fake_Ipcs_Importer(); $importer->rows = [ (object) [ - 'id' => 5, - 'title' => 'Custom Hook', - 'content' => " 1, + 'id' => 5, + 'title' => 'Custom Hook', + 'content' => ' 1, ], (object) [ - 'id' => 6, - 'title' => '', - 'content' => ' 0, + 'id' => 6, + 'title' => '', + 'content' => ' 0, ], ]; $scanner = new Insert_PHP_Code_Snippet_Scanner( $importer ); - - $this->assertSame( 'high', $scanner->get_risk_level() ); - $results = $scanner->scan(); - $this->assertCount( 2, $results ); - $this->assertSame( 'Custom Hook', $results[0]->name ); - $this->assertSame( 'php', $results[0]->type ); - $this->assertSame( 'db://xyz_ips_short_code/5', $results[0]->source_path ); + $this->assertSame( 'high', $scanner->get_risk_level() ); $this->assertTrue( $results[0]->is_active ); - $this->assertSame( 'high', $results[0]->risk_level ); - - $this->assertSame( 'Insert PHP #6', $results[1]->name ); $this->assertFalse( $results[1]->is_active ); + $this->assertSame( 'Insert PHP #6', $results[1]->name ); } - /** - * Adapters short-circuit when the source plugin is inactive. - */ public function test_adapter_returns_empty_when_unavailable() { if ( ! function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; From af3823afa87248b2e0825165cc8afb3de621a03d Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 22 Apr 2026 21:57:57 +0200 Subject: [PATCH 3/3] Unified Snippets: implement Tier 2 DB scanners --- .../Adapters/DB_Scanner_Adapter.php | 18 ----------------- tests/phpunit/fakes/Fake_Hfcm_Importer.php | 20 ------------------- tests/phpunit/fakes/Fake_Ihaf_Importer.php | 20 ------------------- tests/phpunit/fakes/Fake_Ipcs_Importer.php | 18 ----------------- 4 files changed, 76 deletions(-) diff --git a/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php b/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php index 3f4b7160..c429c30e 100644 --- a/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php +++ b/src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php @@ -9,36 +9,18 @@ /** * Adapts an existing {@see Plugin_Importer} into a Unified Snippets scanner. * - * Concrete adapters supply the backing importer and a per-row field mapping; - * this base handles availability, iteration, and Discovered_Snippet assembly. - * * @package Code_Snippets */ abstract class DB_Scanner_Adapter extends Scanner_Base { - /** - * Backing plugin importer used to read raw snippet rows. - * - * @var Plugin_Importer - */ protected Plugin_Importer $importer; public function __construct( ?Plugin_Importer $importer = null ) { $this->importer = $importer ?? $this->create_importer(); } - /** - * Create the default importer instance for this adapter. - * - * @return Plugin_Importer - */ abstract protected function create_importer(): Plugin_Importer; - /** - * Name of the source plugin's database table, used to build synthetic URIs. - * - * @return string e.g. 'hfcm_scripts'. - */ abstract protected function get_table_name(): string; /** diff --git a/tests/phpunit/fakes/Fake_Hfcm_Importer.php b/tests/phpunit/fakes/Fake_Hfcm_Importer.php index d22ea3d7..ce71c374 100644 --- a/tests/phpunit/fakes/Fake_Hfcm_Importer.php +++ b/tests/phpunit/fakes/Fake_Hfcm_Importer.php @@ -4,32 +4,12 @@ use Code_Snippets\REST_API\Import\Plugins\Header_Footer_Code_Manager_Plugin_Importer; -/** - * Fake importer for Header Footer Code Manager used in DB-scanner adapter tests. - */ class Fake_Hfcm_Importer extends Header_Footer_Code_Manager_Plugin_Importer { - /** - * Canned data returned by {@see self::get_data()}. - * - * @var array> - */ - public array $rows = []; - - /** - * {@inheritDoc} - */ public static function is_active(): bool { return true; } - /** - * Return the canned rows regardless of the requested IDs. - * - * @param array $ids_to_import Unused. - * - * @return array> - */ public function get_data( array $ids_to_import = [] ): array { return $this->rows; } diff --git a/tests/phpunit/fakes/Fake_Ihaf_Importer.php b/tests/phpunit/fakes/Fake_Ihaf_Importer.php index d9d1b599..f17e709e 100644 --- a/tests/phpunit/fakes/Fake_Ihaf_Importer.php +++ b/tests/phpunit/fakes/Fake_Ihaf_Importer.php @@ -4,34 +4,14 @@ use Code_Snippets\REST_API\Import\Plugins\Insert_Headers_And_Footers_Plugin_Importer; -/** - * Fake importer for WPCode (Insert Headers and Footers) used in DB-scanner adapter tests. - * - * Overrides the two entry points the scanner uses so no plugin/database is required. - */ class Fake_Ihaf_Importer extends Insert_Headers_And_Footers_Plugin_Importer { - /** - * Canned data returned by {@see self::get_data()}. - * - * @var array> - */ public array $rows = []; - /** - * {@inheritDoc} - */ public static function is_active(): bool { return true; } - /** - * Return the canned rows regardless of the requested IDs. - * - * @param array $ids_to_import Unused. - * - * @return array> - */ public function get_data( array $ids_to_import = [] ): array { return $this->rows; } diff --git a/tests/phpunit/fakes/Fake_Ipcs_Importer.php b/tests/phpunit/fakes/Fake_Ipcs_Importer.php index 354eebba..7d999c22 100644 --- a/tests/phpunit/fakes/Fake_Ipcs_Importer.php +++ b/tests/phpunit/fakes/Fake_Ipcs_Importer.php @@ -4,32 +4,14 @@ use Code_Snippets\REST_API\Import\Plugins\Insert_PHP_Code_Snippet_Plugin_Importer; -/** - * Fake importer for Insert PHP Code Snippet used in DB-scanner adapter tests. - */ class Fake_Ipcs_Importer extends Insert_PHP_Code_Snippet_Plugin_Importer { - /** - * Canned data returned by {@see self::get_data()}. - * - * @var array - */ public array $rows = []; - /** - * {@inheritDoc} - */ public static function is_active(): bool { return true; } - /** - * Return the canned rows regardless of the requested IDs. - * - * @param array $ids_to_import Unused. - * - * @return array - */ public function get_data( array $ids_to_import = [] ): array { return $this->rows; }