Skip to content
Merged
17 changes: 17 additions & 0 deletions src/php/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
use Code_Snippets\UnifiedSnippets\Scanner_Registry;
use Code_Snippets\UnifiedSnippets\Scan_Results_Store;
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;

/**
* The main plugin class
Expand Down Expand Up @@ -169,6 +177,15 @@ private function init_unified_snippets(): void {
$this->unified_snippets = new Scanner_Registry();
$this->unified_snippets_store = new Scan_Results_Store();

$this->unified_snippets->register( new Functions_Php_Scanner() );
$this->unified_snippets->register( new Additional_CSS_Scanner() );
$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 );
}

Expand Down
109 changes: 109 additions & 0 deletions src/php/UnifiedSnippets/Adapters/DB_Scanner_Adapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Code_Snippets\UnifiedSnippets\Adapters;

use Code_Snippets\REST_API\Import\Plugins\Plugin_Importer;
use Code_Snippets\UnifiedSnippets\Model\Discovered_Snippet;
use Code_Snippets\UnifiedSnippets\Scanner_Base;

/**
* Adapts an existing {@see Plugin_Importer} into a Unified Snippets scanner.
*
* @package Code_Snippets
*/
abstract class DB_Scanner_Adapter extends Scanner_Base {

protected Plugin_Importer $importer;

public function __construct( ?Plugin_Importer $importer = null ) {
$this->importer = $importer ?? $this->create_importer();
}

abstract protected function create_importer(): Plugin_Importer;

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<string, mixed> $row Raw row cast to associative array.
*
* @return array<string, mixed>|null
*/
abstract protected function map_row( array $row ): ?array;

public function is_available(): bool {
return (bool) call_user_func( [ get_class( $this->importer ), 'is_active' ] );
}

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;
}

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.
*
* @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';
}
}
}
110 changes: 110 additions & 0 deletions src/php/UnifiedSnippets/Filesystem_Reader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace Code_Snippets\UnifiedSnippets;

use WP_Filesystem_Base;

/**
* Thin read-only wrapper around the WP_Filesystem API used by the Tier 1 scanners.
*
* The WP_Filesystem instance is initialised lazily and shared across all scanners
* in a request. When the API is unavailable (for example during early bootstrap
* or in isolated unit tests), the reader falls back to native PHP file reads so
* scanners can still function.
*
* @package Code_Snippets
*/
class Filesystem_Reader {

/**
* Shared WP_Filesystem instance, or null if unavailable.
*
* @var WP_Filesystem_Base|null
*/
private static ?WP_Filesystem_Base $fs = null;

/**
* Whether initialisation has been attempted this request.
*
* @var bool
*/
private static bool $initialised = false;

/**
* Read the contents of a file.
*
* @param string $path Absolute path to the file.
*
* @return string|null File contents, or null if the file could not be read.
*/
public static function get_contents( string $path ): ?string {
$fs = self::get_fs();

if ( $fs && $fs->exists( $path ) ) {
$contents = $fs->get_contents( $path );
return false === $contents ? null : $contents;
}

if ( ! is_readable( $path ) ) {
return null;
}

// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$contents = file_get_contents( $path );
return false === $contents ? null : $contents;
}

/**
* Determine whether a path is readable through the filesystem API.
*
* @param string $path Absolute path.
*
* @return bool
*/
public static function is_readable( string $path ): bool {
$fs = self::get_fs();

if ( $fs ) {
return $fs->is_readable( $path );
}

return is_readable( $path );
}

/**
* Lazily initialise and retrieve the shared WP_Filesystem instance.
*
* @return WP_Filesystem_Base|null
*/
private static function get_fs(): ?WP_Filesystem_Base {
if ( self::$initialised ) {
return self::$fs;
}

self::$initialised = true;

if ( ! defined( 'ABSPATH' ) ) {
return null;
}

if ( ! function_exists( 'WP_Filesystem' ) ) {
$includes = ABSPATH . 'wp-admin/includes/file.php';
if ( ! is_readable( $includes ) ) {
return null;
}
require_once $includes;
}

if ( ! WP_Filesystem() ) {
return null;
}

global $wp_filesystem;

if ( $wp_filesystem instanceof WP_Filesystem_Base ) {
self::$fs = $wp_filesystem;
}

return self::$fs;
}
}
78 changes: 78 additions & 0 deletions src/php/UnifiedSnippets/Scanners/Additional_CSS_Scanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Code_Snippets\UnifiedSnippets\Scanners;

use Code_Snippets\UnifiedSnippets\Scanner_Base;
use WP_Post;

/**
* Scans the WordPress Customizer "Additional CSS" for the active theme.
*
* @package Code_Snippets
*/
class Additional_CSS_Scanner extends Scanner_Base {

/**
* {@inheritDoc}
*/
public function get_id(): string {
return 'additional-css';
}

/**
* {@inheritDoc}
*/
public function get_label(): string {
return __( 'Additional CSS (Customizer)', 'code-snippets' );
}

/**
* {@inheritDoc}
*/
public function is_available(): bool {
return function_exists( 'wp_get_custom_css_post' );
}

/**
* {@inheritDoc}
*
* Customizer CSS is always low-risk regardless of the base default.
*/
public function get_risk_level(): string {
return 'low';
}

/**
* {@inheritDoc}
*/
public function scan(): array {
$post = wp_get_custom_css_post();

if ( ! $post instanceof WP_Post || '' === trim( (string) $post->post_content ) ) {
return [];
}

$stylesheet = get_stylesheet();
$theme_name = function_exists( 'wp_get_theme' ) ? wp_get_theme()->get( 'Name' ) : $stylesheet;

return [
$this->build_snippet(
[
'name' => sprintf(
/* translators: %s: theme name */
__( 'Additional CSS (%s)', 'code-snippets' ),
$theme_name
),
'code' => $post->post_content,
'type' => 'css',
'source_type' => 'customizer',
'source_name' => $theme_name,
'source_path' => 'customizer://custom_css/' . $stylesheet,
'line_start' => 0,
'line_end' => 0,
'is_active' => true,
]
),
];
}
}
Loading
Loading