From 10351061200bf2cd28a382b914ce33b9c3fe1317 Mon Sep 17 00:00:00 2001 From: lightbulbman Date: Fri, 14 Mar 2025 00:33:19 +0000 Subject: [PATCH 001/106] #228 html validation bug fix --- src/php/snippet-ops.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 2e3a9023..6e189d32 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -295,10 +295,12 @@ function activate_snippet( int $id, ?bool $network = null ) { // translators: %d: snippet identifier. return sprintf( __( 'Could not locate snippet with ID %d.', 'code-snippets' ), $id ); } - - $validator = new Validator( $snippet->code ); - if ( $validator->validate() ) { - return __( 'Could not activate snippet: code did not pass validation.', 'code-snippets' ); + + if('php' == $snippet->type ){ + $validator = new Validator( $snippet->code ); + if ( $validator->validate() ) { + return __( 'Could not activate snippet: code did not pass validation.', 'code-snippets' ); + } } $result = $wpdb->update( From b2be2b83233e1c8a4689842871ef72a007909b7d Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Tue, 1 Jul 2025 22:43:51 +0200 Subject: [PATCH 002/106] wip --- src/php/class-db.php | 10 +++ src/php/class-plugin.php | 4 + src/php/class-snippet-files.php | 125 ++++++++++++++++++++++++++++++++ src/php/class-snippet.php | 2 +- src/php/load.php | 3 +- src/php/snippet-ops.php | 58 ++++++++++++++- 6 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 src/php/class-snippet-files.php diff --git a/src/php/class-db.php b/src/php/class-db.php index 64ea5f47..a0b50782 100644 --- a/src/php/class-db.php +++ b/src/php/class-db.php @@ -283,4 +283,14 @@ function ( $snippet ) use ( $active_shared_ids ) { return $active_snippets; } + + public function get_active_tables(): array { + $active_tables = array( $this->table ); + + if ( is_multisite() ) { + $active_tables[] = $this->ms_table; + } + + return $active_tables; + } } diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index 92bebfa3..32a679db 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -119,6 +119,10 @@ public function load_plugin() { // Cloud List Table shared functions. require_once $includes_path . '/cloud/list-table-shared-ops.php'; + // Snippet files. + require_once $includes_path . '/class-snippet-files.php'; + ( new Snippet_Files() )->register_hooks(); + $this->active_snippets = new Active_Snippets(); $this->front_end = new Front_End(); $this->cloud_api = new Cloud_API(); diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php new file mode 100644 index 00000000..f319b5ee --- /dev/null +++ b/src/php/class-snippet-files.php @@ -0,0 +1,125 @@ +get_type() ) { + return; + } + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + + if ( ! is_dir( $base_dir ) ) { + wp_mkdir_p( $base_dir ); + } + + $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + + if ( $snippet->active ) { + $content = "code; + + file_put_contents( $file_path, $content ); + } else { + @unlink( $file_path ); + } + + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + + $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + + if ( $snippet->active ) { + $active_snippets[ $snippet->id ] = $snippet->get_fields(); + } else { + unset( $active_snippets[ $snippet->id ] ); + } + + $index_content = "get_type() ) { + return; + } + + $table = code_snippets()->db->get_table_name( $network ); + + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + + $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + + @unlink( $file_path ); + + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + + $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + + unset( $active_snippets[ $snippet_id ] ); + + $index_content = "get_type() ) { + return; + } + + $table = code_snippets()->db->get_table_name( $network ); + + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + + $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + + $content = "code; + + file_put_contents( $file_path, $content ); + + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + + $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + + $active_snippets[ $snippet->id ] = $snippet->get_fields(); + + $index_content = "get_type() ) { + return; + } + + $table = code_snippets()->db->get_table_name( $network ); + + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + + $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + + @unlink( $file_path ); + + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + + $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + + unset( $active_snippets[ $snippet_id ] ); + + $index_content = "scope, -4 ) ) { return 'css'; } elseif ( '-js' === substr( $this->scope, -3 ) ) { diff --git a/src/php/load.php b/src/php/load.php index 2de59ce2..8700186a 100644 --- a/src/php/load.php +++ b/src/php/load.php @@ -65,4 +65,5 @@ function code_snippets(): Plugin { code_snippets()->load_plugin(); // Execute the snippets once the plugins are loaded. -add_action( 'plugins_loaded', __NAMESPACE__ . '\execute_active_snippets', 1 ); +// add_action( 'plugins_loaded', __NAMESPACE__ . '\execute_active_snippets', 1 ); +add_action( 'plugins_loaded', __NAMESPACE__ . '\execute_active_snippets_from_flat_files', 1 ); diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 2e3a9023..fd8c391e 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -314,7 +314,7 @@ function activate_snippet( int $id, ?bool $network = null ) { } update_shared_network_snippets( [ $snippet ] ); - do_action( 'code_snippets/activate_snippet', $snippet ); + do_action( 'code_snippets/activate_snippet', $snippet, $network ); clean_snippets_cache( $table_name ); return $snippet; } @@ -392,7 +392,7 @@ function deactivate_snippet( int $id, ?bool $network = null ): ?Snippet { $network = DB::validate_network_param( $network ); $table = code_snippets()->db->get_table_name( $network ); - // Set the snippet to active. + // Set the snippet to inactive. $result = $wpdb->update( $table, array( 'active' => '0' ), @@ -433,6 +433,8 @@ function delete_snippet( int $id, ?bool $network = null ): bool { $network = DB::validate_network_param( $network ); $table = code_snippets()->db->get_table_name( $network ); + $snippet = get_snippet( $id, $network ); + $result = $wpdb->delete( $table, array( 'id' => $id ), @@ -440,7 +442,7 @@ function delete_snippet( int $id, ?bool $network = null ): bool { ); if ( $result ) { - do_action( 'code_snippets/delete_snippet', $id, $network ); + do_action( 'code_snippets/delete_snippet', $snippet, $network ); clean_snippets_cache( $table ); code_snippets()->cloud_api->delete_snippet_from_transient_data( $id ); } @@ -670,6 +672,56 @@ function execute_active_snippets(): bool { return true; } +function execute_active_snippets_from_flat_files(): bool { + $tables = code_snippets()->db->get_active_tables(); + $scopes = array( 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ); + + foreach ( $tables as $table ) { + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + + if ( ! is_dir( $base_dir ) ) { + continue; + } + + $active_snippets_file_path = $base_dir . '/index.php'; + if ( ! is_file( $active_snippets_file_path ) ) { + continue; + } + + $active_snippets = require $active_snippets_file_path; + $sorted_snippets = sort_by_priority( $active_snippets ); + + foreach ( $sorted_snippets as $snippet_id => $snippet_data ) { + if ( ! in_array( $snippet_data['scope'], $scopes, true ) ) { + continue; + } + + $file = $base_dir . '/' . $snippet_id . '.php'; + execute_snippet_from_flat_file( $file, $snippet_id ); + } + } + + return true; +} + +function sort_by_priority( array $snippets ): array { + uasort( $snippets, function ( $a, $b ) { + return $a['priority'] <=> $b['priority']; + } ); + + return $snippets; +} + +function execute_snippet_from_flat_file( $file, int $id = 0, bool $force = false ) { + if ( ! is_file( $file ) || ( ! $force && defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) ) { + return false; + } + + require_once $file; + + do_action( 'code_snippets/after_execute_snippet_from_flat_file', $file, $id ); +} + /** * Retrieve a single snippets from the database using its cloud ID. * From 510e49e69da1ff6eb0c384469d97e09d46dcba84 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 09:15:12 +0200 Subject: [PATCH 003/106] wip --- src/php/class-snippet-files.php | 154 ++++++++++++++++---------------- 1 file changed, 76 insertions(+), 78 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index f319b5ee..00e2f375 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -4,122 +4,120 @@ class Snippet_Files { - public function register_hooks() { - add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); - add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); - add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); - add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); - add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); - } + public function register_hooks() { + add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); + add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); + add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); + add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); + add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); + } - public function handle_snippet( $snippet, $table ) { - if ( 'php' !== $snippet->get_type() ) { - return; - } - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + public function handle_snippet( $snippet, $table ) { + if ( 'php' !== $snippet->get_type() ) { + return; + } + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; - if ( ! is_dir( $base_dir ) ) { - wp_mkdir_p( $base_dir ); - } + if ( ! is_dir( $base_dir ) ) { + wp_mkdir_p( $base_dir ); + } - $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; - if ( $snippet->active ) { - $content = "code; + if ( $snippet->active ) { + $content = "code; - file_put_contents( $file_path, $content ); - } else { - @unlink( $file_path ); - } + file_put_contents( $file_path, $content ); + } else { + @unlink( $file_path ); + } - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; - if ( $snippet->active ) { - $active_snippets[ $snippet->id ] = $snippet->get_fields(); - } else { - unset( $active_snippets[ $snippet->id ] ); - } - - $index_content = "active ) { + $active_snippets[ $snippet->id ] = $snippet->get_fields(); + } else { + unset( $active_snippets[ $snippet->id ] ); + } - public function delete_snippet( $snippet, $network ) { - if ( 'php' !== $snippet->get_type() ) { - return; - } + $index_content = "db->get_table_name( $network ); + file_put_contents( $index_file_path, $index_content ); + } - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + public function delete_snippet( $snippet, $network ) { + if ( 'php' !== $snippet->get_type() ) { + return; + } - $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + $table = code_snippets()->db->get_table_name( $network ); - @unlink( $file_path ); + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + @unlink( $file_path ); - unset( $active_snippets[ $snippet_id ] ); + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; - $index_content = "get_type() ) { - return; - } + $index_content = "db->get_table_name( $network ); + file_put_contents( $index_file_path, $index_content ); + } - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + public function activate_snippet( $snippet, $network ) { + if ( 'php' !== $snippet->get_type() ) { + return; + } - $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + $table = code_snippets()->db->get_table_name( $network ); - $content = "code; + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; - file_put_contents( $file_path, $content ); + $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + $content = "code; - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + file_put_contents( $file_path, $content ); - $active_snippets[ $snippet->id ] = $snippet->get_fields(); + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; - $index_content = "id ] = $snippet->get_fields(); - public function deactivate_snippet( $snippet_id, $network ) { - $snippet = get_snippet( $snippet_id, $network ); + $index_content = "get_type() ) { - return; - } + file_put_contents( $index_file_path, $index_content ); + } - $table = code_snippets()->db->get_table_name( $network ); + public function deactivate_snippet( $snippet_id, $network ) { + $snippet = get_snippet( $snippet_id, $network ); - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + if ( 'php' !== $snippet->get_type() ) { + return; + } - $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $network; - @unlink( $file_path ); + $file_path = trailingslashit( $base_dir ) . $snippet_id . '.php'; - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + @unlink( $file_path ); - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; - unset( $active_snippets[ $snippet_id ] ); + $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; - $index_content = " Date: Wed, 2 Jul 2025 09:29:04 +0200 Subject: [PATCH 004/106] wip --- src/php/class-snippet-files.php | 166 +++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 58 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 00e2f375..d81572a9 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -4,6 +4,29 @@ class Snippet_Files { + /** + * Holds the WP_Filesystem instance. + * + * @var \WP_Filesystem_Base + */ + private $fs; + + public function __construct() { + $this->init_filesystem(); + } + + /** + * Initialize WP_Filesystem. + */ + private function init_filesystem() { + if ( ! function_exists( 'WP_Filesystem' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + WP_Filesystem(); + global $wp_filesystem; + $this->fs = $wp_filesystem; + } + public function register_hooks() { add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); @@ -16,35 +39,19 @@ public function handle_snippet( $snippet, $table ) { if ( 'php' !== $snippet->get_type() ) { return; } - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; - if ( ! is_dir( $base_dir ) ) { - wp_mkdir_p( $base_dir ); - } + $base_dir = $this->get_base_dir( $table ); + $this->maybe_create_directory( $base_dir ); - $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); if ( $snippet->active ) { - $content = "code; - - file_put_contents( $file_path, $content ); + $this->write_snippet_file( $file_path, $snippet->code ); } else { - @unlink( $file_path ); + $this->delete_file( $file_path ); } - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; - - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; - - if ( $snippet->active ) { - $active_snippets[ $snippet->id ] = $snippet->get_fields(); - } else { - unset( $active_snippets[ $snippet->id ] ); - } - - $index_content = "update_index_file( $base_dir, $snippet, $snippet->active ); } public function delete_snippet( $snippet, $network ) { @@ -53,22 +60,12 @@ public function delete_snippet( $snippet, $network ) { } $table = code_snippets()->db->get_table_name( $network ); + $base_dir = $this->get_base_dir( $table ); - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; - - $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; - - @unlink( $file_path ); + $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); + $this->delete_file( $file_path ); - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; - - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; - - unset( $active_snippets[ $snippet_id ] ); - - $index_content = "update_index_file( $base_dir, $snippet, false ); } public function activate_snippet( $snippet, $network ) { @@ -77,47 +74,100 @@ public function activate_snippet( $snippet, $network ) { } $table = code_snippets()->db->get_table_name( $network ); + $base_dir = $this->get_base_dir( $table ); - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; - - $file_path = trailingslashit( $base_dir ) . $snippet->id . '.php'; + $this->maybe_create_directory( $base_dir ); - $content = "code; + $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); + $this->write_snippet_file( $file_path, $snippet->code ); - file_put_contents( $file_path, $content ); + $this->update_index_file( $base_dir, $snippet, true ); + } - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + public function deactivate_snippet( $snippet_id, $network ) { + $snippet = get_snippet( $snippet_id, $network ); - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + if ( ! $snippet || 'php' !== $snippet->get_type() ) { + return; + } - $active_snippets[ $snippet->id ] = $snippet->get_fields(); + $table = code_snippets()->db->get_table_name( $network ); + $base_dir = $this->get_base_dir( $table ); - $index_content = "get_snippet_file_path( $base_dir, $snippet_id ); + $this->delete_file( $file_path ); - file_put_contents( $index_file_path, $index_content ); + $this->update_index_file( $base_dir, $snippet, false ); } - public function deactivate_snippet( $snippet_id, $network ) { - $snippet = get_snippet( $snippet_id, $network ); + /** + * Returns the base directory path for a given table. + */ + private function get_base_dir( $table ) { + return WP_CONTENT_DIR . '/code-snippets/' . $table; + } - if ( 'php' !== $snippet->get_type() ) { - return; + /** + * Creates the directory if it does not exist. + */ + private function maybe_create_directory( $dir ) { + if ( ! $this->fs->is_dir( $dir ) ) { + $this->fs->mkdir( $dir, FS_CHMOD_DIR ); } + } - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $network; + /** + * Returns the path to the snippet PHP file. + */ + private function get_snippet_file_path( $base_dir, $snippet_id ) { + return trailingslashit( $base_dir ) . $snippet_id . '.php'; + } - $file_path = trailingslashit( $base_dir ) . $snippet_id . '.php'; + /** + * Writes the snippet code to a file, with the required header. + */ + private function write_snippet_file( $file_path, $code ) { + $content = "fs->put_contents( $file_path, $content, FS_CHMOD_FILE ); + } - @unlink( $file_path ); + /** + * Deletes a file if it exists. + */ + private function delete_file( $file_path ) { + if ( $this->fs->exists( $file_path ) ) { + $this->fs->delete( $file_path ); + } + } - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + /** + * Loads the index.php array by requiring it directly. + */ + private function load_index_file( $index_file_path ) { + return is_file( $index_file_path ) ? require $index_file_path : []; + } - $active_snippets = is_file( $index_file_path ) ? require $index_file_path : []; + /** + * Saves the index.php file via WP_Filesystem. + */ + private function save_index_file( $index_file_path, $active_snippets ) { + $index_content = "fs->put_contents( $index_file_path, $index_content, FS_CHMOD_FILE ); + } - unset( $active_snippets[ $snippet_id ] ); + /** + * Updates the index.php file by adding or removing a snippet. + */ + private function update_index_file( $base_dir, $snippet, $active ) { + $index_file_path = trailingslashit( $base_dir ) . 'index.php'; + $active_snippets = $this->load_index_file( $index_file_path ); - $index_content = "id ] = $snippet->get_fields(); + } else { + unset( $active_snippets[ $snippet->id ] ); + } - file_put_contents( $index_file_path, $index_content ); + $this->save_index_file( $index_file_path, $active_snippets ); } } From f65f5bc6f325392b0de9b75054d5afdb88a5ad57 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 09:43:56 +0200 Subject: [PATCH 005/106] wip --- src/php/class-plugin.php | 2 +- src/php/class-snippet-files.php | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index 32a679db..fda65d6f 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -121,7 +121,7 @@ public function load_plugin() { // Snippet files. require_once $includes_path . '/class-snippet-files.php'; - ( new Snippet_Files() )->register_hooks(); + ( new Snippet_Files() )->init(); $this->active_snippets = new Active_Snippets(); $this->front_end = new Front_End(); diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index d81572a9..4cb1b9b5 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -11,20 +11,9 @@ class Snippet_Files { */ private $fs; - public function __construct() { - $this->init_filesystem(); - } - - /** - * Initialize WP_Filesystem. - */ - private function init_filesystem() { - if ( ! function_exists( 'WP_Filesystem' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - } - WP_Filesystem(); - global $wp_filesystem; - $this->fs = $wp_filesystem; + public function init() { + $this->ensure_filesystem(); + $this->register_hooks(); } public function register_hooks() { @@ -35,6 +24,17 @@ public function register_hooks() { add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); } + private function ensure_filesystem() { + if ( ! $this->fs ) { + if ( ! function_exists( 'WP_Filesystem' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + WP_Filesystem(); + global $wp_filesystem; + $this->fs = $wp_filesystem; + } + } + public function handle_snippet( $snippet, $table ) { if ( 'php' !== $snippet->get_type() ) { return; From c7f3d3cb49245f2fc1fd56e3b6b3204f8219f5d2 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 09:46:42 +0200 Subject: [PATCH 006/106] wip --- src/php/snippet-ops.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index fd8c391e..5927ff7b 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -689,7 +689,7 @@ function execute_active_snippets_from_flat_files(): bool { } $active_snippets = require $active_snippets_file_path; - $sorted_snippets = sort_by_priority( $active_snippets ); + $sorted_snippets = cs_sort_snippets_by_priority( $active_snippets ); foreach ( $sorted_snippets as $snippet_id => $snippet_data ) { if ( ! in_array( $snippet_data['scope'], $scopes, true ) ) { @@ -704,7 +704,7 @@ function execute_active_snippets_from_flat_files(): bool { return true; } -function sort_by_priority( array $snippets ): array { +function cs_sort_snippets_by_priority( array $snippets ): array { uasort( $snippets, function ( $a, $b ) { return $a['priority'] <=> $b['priority']; } ); From 29a540c1b29a1d4c1017de3923445d511a1b5cb0 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 09:58:00 +0200 Subject: [PATCH 007/106] wip --- src/php/class-snippet-files.php | 24 ++++++++++++++---------- src/php/snippet-ops.php | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 4cb1b9b5..bee41b59 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -36,11 +36,12 @@ private function ensure_filesystem() { } public function handle_snippet( $snippet, $table ) { - if ( 'php' !== $snippet->get_type() ) { + $snippet_type = $snippet->get_type(); + if ( 'php' !== $snippet_type ) { return; } - $base_dir = $this->get_base_dir( $table ); + $base_dir = $this->get_base_dir( $table, $snippet_type ); $this->maybe_create_directory( $base_dir ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); @@ -55,12 +56,13 @@ public function handle_snippet( $snippet, $table ) { } public function delete_snippet( $snippet, $network ) { - if ( 'php' !== $snippet->get_type() ) { + $snippet_type = $snippet->get_type(); + if ( 'php' !== $snippet_type ) { return; } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = $this->get_base_dir( $table ); + $base_dir = $this->get_base_dir( $table, $snippet_type ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); $this->delete_file( $file_path ); @@ -69,12 +71,13 @@ public function delete_snippet( $snippet, $network ) { } public function activate_snippet( $snippet, $network ) { - if ( 'php' !== $snippet->get_type() ) { + $snippet_type = $snippet->get_type(); + if ( 'php' !== $snippet_type ) { return; } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = $this->get_base_dir( $table ); + $base_dir = $this->get_base_dir( $table, $snippet_type ); $this->maybe_create_directory( $base_dir ); @@ -86,13 +89,14 @@ public function activate_snippet( $snippet, $network ) { public function deactivate_snippet( $snippet_id, $network ) { $snippet = get_snippet( $snippet_id, $network ); + $snippet_type = $snippet->get_type(); - if ( ! $snippet || 'php' !== $snippet->get_type() ) { + if ( ! $snippet || 'php' !== $snippet_type ) { return; } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = $this->get_base_dir( $table ); + $base_dir = $this->get_base_dir( $table, $snippet_type ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet_id ); $this->delete_file( $file_path ); @@ -103,8 +107,8 @@ public function deactivate_snippet( $snippet_id, $network ) { /** * Returns the base directory path for a given table. */ - private function get_base_dir( $table ) { - return WP_CONTENT_DIR . '/code-snippets/' . $table; + private function get_base_dir( $table, $snippet_type ) { + return WP_CONTENT_DIR . '/code-snippets/' . $table . '/' . $snippet_type; } /** diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 5927ff7b..7cf791bd 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -677,7 +677,7 @@ function execute_active_snippets_from_flat_files(): bool { $scopes = array( 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ); foreach ( $tables as $table ) { - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table; + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table . '/php'; if ( ! is_dir( $base_dir ) ) { continue; From c47c344cc004ddac7d961b7d59833040b7c81563 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 10:01:54 +0200 Subject: [PATCH 008/106] wip --- src/php/class-snippet-files.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index bee41b59..bd313cde 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -36,7 +36,7 @@ private function ensure_filesystem() { } public function handle_snippet( $snippet, $table ) { - $snippet_type = $snippet->get_type(); + $snippet_type = $snippet->get_type(); if ( 'php' !== $snippet_type ) { return; } @@ -56,7 +56,7 @@ public function handle_snippet( $snippet, $table ) { } public function delete_snippet( $snippet, $network ) { - $snippet_type = $snippet->get_type(); + $snippet_type = $snippet->get_type(); if ( 'php' !== $snippet_type ) { return; } @@ -71,7 +71,7 @@ public function delete_snippet( $snippet, $network ) { } public function activate_snippet( $snippet, $network ) { - $snippet_type = $snippet->get_type(); + $snippet_type = $snippet->get_type(); if ( 'php' !== $snippet_type ) { return; } @@ -89,7 +89,7 @@ public function activate_snippet( $snippet, $network ) { public function deactivate_snippet( $snippet_id, $network ) { $snippet = get_snippet( $snippet_id, $network ); - $snippet_type = $snippet->get_type(); + $snippet_type = $snippet->get_type(); if ( ! $snippet || 'php' !== $snippet_type ) { return; From b66db45dde19a4092bac879ebfc90460acc8fcd9 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 10:46:05 +0200 Subject: [PATCH 009/106] wip --- src/php/class-snippet-files.php | 23 +++++++++++++++-------- src/php/front-end/class-front-end.php | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index bd313cde..6fdadfcb 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -37,7 +37,7 @@ private function ensure_filesystem() { public function handle_snippet( $snippet, $table ) { $snippet_type = $snippet->get_type(); - if ( 'php' !== $snippet_type ) { + if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { return; } @@ -47,7 +47,7 @@ public function handle_snippet( $snippet, $table ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); if ( $snippet->active ) { - $this->write_snippet_file( $file_path, $snippet->code ); + $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); } else { $this->delete_file( $file_path ); } @@ -57,7 +57,7 @@ public function handle_snippet( $snippet, $table ) { public function delete_snippet( $snippet, $network ) { $snippet_type = $snippet->get_type(); - if ( 'php' !== $snippet_type ) { + if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { return; } @@ -72,7 +72,7 @@ public function delete_snippet( $snippet, $network ) { public function activate_snippet( $snippet, $network ) { $snippet_type = $snippet->get_type(); - if ( 'php' !== $snippet_type ) { + if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { return; } @@ -82,7 +82,7 @@ public function activate_snippet( $snippet, $network ) { $this->maybe_create_directory( $base_dir ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); - $this->write_snippet_file( $file_path, $snippet->code ); + $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); $this->update_index_file( $base_dir, $snippet, true ); } @@ -91,7 +91,7 @@ public function deactivate_snippet( $snippet_id, $network ) { $snippet = get_snippet( $snippet_id, $network ); $snippet_type = $snippet->get_type(); - if ( ! $snippet || 'php' !== $snippet_type ) { + if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { return; } @@ -130,8 +130,15 @@ private function get_snippet_file_path( $base_dir, $snippet_id ) { /** * Writes the snippet code to a file, with the required header. */ - private function write_snippet_file( $file_path, $code ) { - $content = "\n\n"; + } + + $content .= $code; + $this->fs->put_contents( $file_path, $content, FS_CHMOD_FILE ); } diff --git a/src/php/front-end/class-front-end.php b/src/php/front-end/class-front-end.php index 8d76ae8a..60429b35 100644 --- a/src/php/front-end/class-front-end.php +++ b/src/php/front-end/class-front-end.php @@ -245,6 +245,16 @@ protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): return $snippet->code; } + $network = DB::validate_network_param( $snippet->network ); + $table_name = code_snippets()->db->get_table_name( $network ); + $filepath = WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/html/' . $snippet->id . '.php'; + + return file_exists( $filepath ) + ? $this->evaluate_shortcode_from_flat_file( $filepath, $atts ) + : $this->evaluate_shortcode_from_db( $snippet, $atts ); + } + + private function evaluate_shortcode_from_db( Snippet $snippet, array $atts ): string { /** * Avoiding extract is typically recommended, however in this situation we want to make it easy for snippet * authors to use custom attributes. @@ -259,6 +269,23 @@ protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): return ob_get_clean(); } + private function evaluate_shortcode_from_flat_file( $filepath, array $atts ): string { + ob_start(); + + ( function( $atts ) use ( $filepath ) { + /** + * Avoiding extract is typically recommended, however in this situation we want to make it easy for snippet + * authors to use custom attributes. + * + * @phpcs:disable WordPress.PHP.DontExtract.extract_extract + */ + extract( $atts ); + require_once $filepath; + } )( $atts ); + + return ob_get_clean(); + } + /** * Render the value of a content shortcode * From 55561ca4d233bcfe94dd66f10de11adc057c984e Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 10:50:19 +0200 Subject: [PATCH 010/106] wip --- src/php/class-snippet-files.php | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 6fdadfcb..92d18ab4 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -52,7 +52,7 @@ public function handle_snippet( $snippet, $table ) { $this->delete_file( $file_path ); } - $this->update_index_file( $base_dir, $snippet, $snippet->active ); + $this->update_config_file( $base_dir, $snippet, $snippet->active ); } public function delete_snippet( $snippet, $network ) { @@ -67,7 +67,7 @@ public function delete_snippet( $snippet, $network ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); $this->delete_file( $file_path ); - $this->update_index_file( $base_dir, $snippet, false ); + $this->update_config_file( $base_dir, $snippet, false ); } public function activate_snippet( $snippet, $network ) { @@ -84,7 +84,7 @@ public function activate_snippet( $snippet, $network ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); - $this->update_index_file( $base_dir, $snippet, true ); + $this->update_config_file( $base_dir, $snippet, true ); } public function deactivate_snippet( $snippet_id, $network ) { @@ -101,7 +101,7 @@ public function deactivate_snippet( $snippet_id, $network ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet_id ); $this->delete_file( $file_path ); - $this->update_index_file( $base_dir, $snippet, false ); + $this->update_config_file( $base_dir, $snippet, false ); } /** @@ -133,9 +133,9 @@ private function get_snippet_file_path( $base_dir, $snippet_id ) { private function write_snippet_file( $file_path, $code, $snippet_type ) { $content = "\n\n"; - } + if ( 'html' === $snippet_type ) { + $content .= "?>\n\n"; + } $content .= $code; @@ -154,24 +154,24 @@ private function delete_file( $file_path ) { /** * Loads the index.php array by requiring it directly. */ - private function load_index_file( $index_file_path ) { - return is_file( $index_file_path ) ? require $index_file_path : []; + private function load_config_file( $config_file_path ) { + return is_file( $config_file_path ) ? require $config_file_path : []; } /** * Saves the index.php file via WP_Filesystem. */ - private function save_index_file( $index_file_path, $active_snippets ) { + private function save_config_file( $config_file_path, $active_snippets ) { $index_content = "fs->put_contents( $index_file_path, $index_content, FS_CHMOD_FILE ); + $this->fs->put_contents( $config_file_path, $index_content, FS_CHMOD_FILE ); } /** * Updates the index.php file by adding or removing a snippet. */ - private function update_index_file( $base_dir, $snippet, $active ) { - $index_file_path = trailingslashit( $base_dir ) . 'index.php'; - $active_snippets = $this->load_index_file( $index_file_path ); + private function update_config_file( $base_dir, $snippet, $active ) { + $config_file_path = trailingslashit( $base_dir ) . 'index.php'; + $active_snippets = $this->load_config_file( $config_file_path ); if ( $active ) { $active_snippets[ $snippet->id ] = $snippet->get_fields(); @@ -179,6 +179,6 @@ private function update_index_file( $base_dir, $snippet, $active ) { unset( $active_snippets[ $snippet->id ] ); } - $this->save_index_file( $index_file_path, $active_snippets ); + $this->save_config_file( $config_file_path, $active_snippets ); } } From f2720ace011c9edd529f4e63cea633d699039910 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 12:39:17 +0200 Subject: [PATCH 011/106] wip --- src/php/class-snippet-files.php | 51 +++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 92d18ab4..1040df23 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -11,17 +11,19 @@ class Snippet_Files { */ private $fs; + const TYPES_TO_HANDLE = [ 'php', 'html' ]; + public function init() { $this->ensure_filesystem(); $this->register_hooks(); } public function register_hooks() { - add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); - add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); - add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); - add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); - add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); + add_action( 'code_snippets/create_snippet', array( $this, 'handle_snippet' ), 10, 2 ); + add_action( 'code_snippets/update_snippet', array( $this, 'handle_snippet' ), 10, 2 ); + add_action( 'code_snippets/delete_snippet', array( $this, 'delete_snippet' ), 10, 2 ); + add_action( 'code_snippets/activate_snippet', array( $this, 'activate_snippet' ), 10, 2 ); + add_action( 'code_snippets/deactivate_snippet', array( $this, 'deactivate_snippet' ), 10, 2 ); } private function ensure_filesystem() { @@ -29,15 +31,22 @@ private function ensure_filesystem() { if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } + WP_Filesystem(); + global $wp_filesystem; + $this->fs = $wp_filesystem; } } - public function handle_snippet( $snippet, $table ) { + private function should_handle_snippet( string $snippet_type ) { + return in_array( $snippet_type, self::TYPES_TO_HANDLE, true ); + } + + public function handle_snippet( Snippet $snippet, string $table ) { $snippet_type = $snippet->get_type(); - if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { + if ( ! $this->should_handle_snippet( $snippet_type ) ) { return; } @@ -55,9 +64,9 @@ public function handle_snippet( $snippet, $table ) { $this->update_config_file( $base_dir, $snippet, $snippet->active ); } - public function delete_snippet( $snippet, $network ) { + public function delete_snippet( Snippet $snippet, bool $network ) { $snippet_type = $snippet->get_type(); - if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { + if ( ! $this->should_handle_snippet( $snippet_type ) ) { return; } @@ -70,9 +79,9 @@ public function delete_snippet( $snippet, $network ) { $this->update_config_file( $base_dir, $snippet, false ); } - public function activate_snippet( $snippet, $network ) { + public function activate_snippet( Snippet $snippet, bool $network ) { $snippet_type = $snippet->get_type(); - if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { + if ( ! $this->should_handle_snippet( $snippet_type ) ) { return; } @@ -87,11 +96,11 @@ public function activate_snippet( $snippet, $network ) { $this->update_config_file( $base_dir, $snippet, true ); } - public function deactivate_snippet( $snippet_id, $network ) { + public function deactivate_snippet( int $snippet_id, bool $network ) { $snippet = get_snippet( $snippet_id, $network ); $snippet_type = $snippet->get_type(); - if ( 'php' !== $snippet_type && 'html' !== $snippet_type ) { + if ( ! $this->should_handle_snippet( $snippet_type ) ) { return; } @@ -107,14 +116,14 @@ public function deactivate_snippet( $snippet_id, $network ) { /** * Returns the base directory path for a given table. */ - private function get_base_dir( $table, $snippet_type ) { + private function get_base_dir( string $table, string $snippet_type ) { return WP_CONTENT_DIR . '/code-snippets/' . $table . '/' . $snippet_type; } /** * Creates the directory if it does not exist. */ - private function maybe_create_directory( $dir ) { + private function maybe_create_directory( string $dir ) { if ( ! $this->fs->is_dir( $dir ) ) { $this->fs->mkdir( $dir, FS_CHMOD_DIR ); } @@ -123,14 +132,14 @@ private function maybe_create_directory( $dir ) { /** * Returns the path to the snippet PHP file. */ - private function get_snippet_file_path( $base_dir, $snippet_id ) { + private function get_snippet_file_path( string $base_dir, int $snippet_id ) { return trailingslashit( $base_dir ) . $snippet_id . '.php'; } /** * Writes the snippet code to a file, with the required header. */ - private function write_snippet_file( $file_path, $code, $snippet_type ) { + private function write_snippet_file( string $file_path, string $code, string $snippet_type ) { $content = "fs->exists( $file_path ) ) { $this->fs->delete( $file_path ); } @@ -154,14 +163,14 @@ private function delete_file( $file_path ) { /** * Loads the index.php array by requiring it directly. */ - private function load_config_file( $config_file_path ) { + private function load_config_file( string $config_file_path ) { return is_file( $config_file_path ) ? require $config_file_path : []; } /** * Saves the index.php file via WP_Filesystem. */ - private function save_config_file( $config_file_path, $active_snippets ) { + private function save_config_file( string $config_file_path, array $active_snippets ) { $index_content = "fs->put_contents( $config_file_path, $index_content, FS_CHMOD_FILE ); } @@ -169,7 +178,7 @@ private function save_config_file( $config_file_path, $active_snippets ) { /** * Updates the index.php file by adding or removing a snippet. */ - private function update_config_file( $base_dir, $snippet, $active ) { + private function update_config_file( string $base_dir, Snippet $snippet, bool $active ) { $config_file_path = trailingslashit( $base_dir ) . 'index.php'; $active_snippets = $this->load_config_file( $config_file_path ); From 6477f86889b33c2b2756b5570c6aa11ac283ce9e Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 13:01:40 +0200 Subject: [PATCH 012/106] wip --- src/php/snippet-ops.php | 68 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 7cf791bd..d6200091 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -659,6 +659,9 @@ function execute_active_snippets(): bool { array( '%d' ) ); clean_snippets_cache( $table_name ); + + $network = $table_name === $db->ms_table; + do_action( 'code_snippets/deactivate_snippet', $snippet_id, $network ); } } @@ -672,12 +675,38 @@ function execute_active_snippets(): bool { return true; } +/** + * Execute the active snippets from the flat files. + * Read-write-execute operation. + * + * @return bool true on success, false on failure. + */ function execute_active_snippets_from_flat_files(): bool { - $tables = code_snippets()->db->get_active_tables(); + $db = code_snippets()->db; + $tables = $db->get_active_tables(); $scopes = array( 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ); - foreach ( $tables as $table ) { - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table . '/php'; + // Detect if a snippet is currently being edited, and if so, spare it from execution. + $edit_id = 0; + $edit_table = $db->table; + + if ( wp_is_json_request() && ! empty( $_SERVER['REQUEST_URI'] ) ) { + $url = wp_parse_url( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); + + if ( isset( $url['path'] ) && false !== strpos( $url['path'], Snippets_REST_Controller::get_prefixed_base_route() ) ) { + $path_parts = explode( '/', $url['path'] ); + $edit_id = intval( end( $path_parts ) ); + + if ( ! empty( $url['query'] ) ) { + wp_parse_str( $url['query'], $path_params ); + $edit_table = isset( $path_params['network'] ) && rest_sanitize_boolean( $path_params['network'] ) ? + $db->ms_table : $db->table; + } + } + } + + foreach ( $tables as $table_name ) { + $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/php'; if ( ! is_dir( $base_dir ) ) { continue; @@ -691,13 +720,40 @@ function execute_active_snippets_from_flat_files(): bool { $active_snippets = require $active_snippets_file_path; $sorted_snippets = cs_sort_snippets_by_priority( $active_snippets ); - foreach ( $sorted_snippets as $snippet_id => $snippet_data ) { - if ( ! in_array( $snippet_data['scope'], $scopes, true ) ) { + foreach ( $sorted_snippets as $snippet_id => $snippet ) { + if ( ! in_array( $snippet['scope'], $scopes, true ) ) { continue; } + // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. + if ( 'single-use' === $snippet['scope'] ) { + $active_shared_ids = get_option( 'active_shared_network_snippets', array() ); + + if ( $table_name === $db->ms_table && is_array( $active_shared_ids ) && in_array( $snippet_id, $active_shared_ids, true ) ) { + unset( $active_shared_ids[ array_search( $snippet_id, $active_shared_ids, true ) ] ); + $active_shared_ids = array_values( $active_shared_ids ); + update_option( 'active_shared_network_snippets', $active_shared_ids ); + clean_active_snippets_cache( $table_name ); + } else { + $wpdb->update( + $table_name, + array( 'active' => '0' ), + array( 'id' => $snippet_id ), + array( '%d' ), + array( '%d' ) + ); + clean_snippets_cache( $table_name ); + + $network = $table_name === $db->ms_table; + do_action( 'code_snippets/deactivate_snippet', $snippet_id, $network ); + } + } + $file = $base_dir . '/' . $snippet_id . '.php'; - execute_snippet_from_flat_file( $file, $snippet_id ); + if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) && + ! ( $edit_id === $snippet_id && $table_name === $edit_table ) ) { + execute_snippet_from_flat_file( $file, $snippet_id ); + } } } From 0eda7c915b2a0e4747954afd34df5ebc77fef6e0 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 13:09:46 +0200 Subject: [PATCH 013/106] wip --- src/php/class-snippet-files.php | 10 +++++----- src/php/snippet-ops.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 1040df23..cf3b7302 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -50,7 +50,7 @@ public function handle_snippet( Snippet $snippet, string $table ) { return; } - $base_dir = $this->get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $snippet_type ); $this->maybe_create_directory( $base_dir ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); @@ -71,7 +71,7 @@ public function delete_snippet( Snippet $snippet, bool $network ) { } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = $this->get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $snippet_type ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); $this->delete_file( $file_path ); @@ -86,7 +86,7 @@ public function activate_snippet( Snippet $snippet, bool $network ) { } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = $this->get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $snippet_type ); $this->maybe_create_directory( $base_dir ); @@ -105,7 +105,7 @@ public function deactivate_snippet( int $snippet_id, bool $network ) { } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = $this->get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $snippet_type ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet_id ); $this->delete_file( $file_path ); @@ -116,7 +116,7 @@ public function deactivate_snippet( int $snippet_id, bool $network ) { /** * Returns the base directory path for a given table. */ - private function get_base_dir( string $table, string $snippet_type ) { + public static function get_base_dir( string $table, string $snippet_type ) { return WP_CONTENT_DIR . '/code-snippets/' . $table . '/' . $snippet_type; } diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index d6200091..dc96a6df 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -706,7 +706,7 @@ function execute_active_snippets_from_flat_files(): bool { } foreach ( $tables as $table_name ) { - $base_dir = WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/php'; + $base_dir = Snippet_Files::get_base_dir( $table_name, 'php' ); if ( ! is_dir( $base_dir ) ) { continue; From 759aabdedf2460adad77b95053e3a9cf334c4a1a Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 13:35:15 +0200 Subject: [PATCH 014/106] wip --- src/php/snippet-ops.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index dc96a6df..91326aec 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -682,6 +682,12 @@ function execute_active_snippets(): bool { * @return bool true on success, false on failure. */ function execute_active_snippets_from_flat_files(): bool { + // Bail early if safe mode is active. + if ( ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) || + ! apply_filters( 'code_snippets/execute_snippets', true ) ) { + return false; + } + $db = code_snippets()->db; $tables = $db->get_active_tables(); $scopes = array( 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ); From 18bcfa399954a92cbe8cda4df9999183944c0707 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 14:40:19 +0200 Subject: [PATCH 015/106] wip --- src/php/snippet-ops.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 91326aec..34d2c787 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -682,6 +682,8 @@ function execute_active_snippets(): bool { * @return bool true on success, false on failure. */ function execute_active_snippets_from_flat_files(): bool { + global $wpdb; + // Bail early if safe mode is active. if ( ( defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) || ! apply_filters( 'code_snippets/execute_snippets', true ) ) { From dc91357dc608e501df27926fe35e385a1c37bc76 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 21:46:20 +0200 Subject: [PATCH 016/106] wip --- src/php/class-snippet-files.php | 37 +++++++++++++-------------------- src/php/snippet-ops.php | 4 ++++ 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index cf3b7302..3aa97f7a 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -19,11 +19,11 @@ public function init() { } public function register_hooks() { - add_action( 'code_snippets/create_snippet', array( $this, 'handle_snippet' ), 10, 2 ); - add_action( 'code_snippets/update_snippet', array( $this, 'handle_snippet' ), 10, 2 ); - add_action( 'code_snippets/delete_snippet', array( $this, 'delete_snippet' ), 10, 2 ); - add_action( 'code_snippets/activate_snippet', array( $this, 'activate_snippet' ), 10, 2 ); - add_action( 'code_snippets/deactivate_snippet', array( $this, 'deactivate_snippet' ), 10, 2 ); + add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); + add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); + add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); + add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); + add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); } private function ensure_filesystem() { @@ -55,13 +55,9 @@ public function handle_snippet( Snippet $snippet, string $table ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); - if ( $snippet->active ) { - $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); - } else { - $this->delete_file( $file_path ); - } + $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); - $this->update_config_file( $base_dir, $snippet, $snippet->active ); + $this->update_config_file( $base_dir, $snippet ); } public function delete_snippet( Snippet $snippet, bool $network ) { @@ -76,7 +72,7 @@ public function delete_snippet( Snippet $snippet, bool $network ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); $this->delete_file( $file_path ); - $this->update_config_file( $base_dir, $snippet, false ); + $this->update_config_file( $base_dir, $snippet, true ); } public function activate_snippet( Snippet $snippet, bool $network ) { @@ -93,7 +89,7 @@ public function activate_snippet( Snippet $snippet, bool $network ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); - $this->update_config_file( $base_dir, $snippet, true ); + $this->update_config_file( $base_dir, $snippet ); } public function deactivate_snippet( int $snippet_id, bool $network ) { @@ -107,10 +103,7 @@ public function deactivate_snippet( int $snippet_id, bool $network ) { $table = code_snippets()->db->get_table_name( $network ); $base_dir = self::get_base_dir( $table, $snippet_type ); - $file_path = $this->get_snippet_file_path( $base_dir, $snippet_id ); - $this->delete_file( $file_path ); - - $this->update_config_file( $base_dir, $snippet, false ); + $this->update_config_file( $base_dir, $snippet ); } /** @@ -176,16 +169,16 @@ private function save_config_file( string $config_file_path, array $active_snipp } /** - * Updates the index.php file by adding or removing a snippet. + * Updates the index.php file with snippet config. */ - private function update_config_file( string $base_dir, Snippet $snippet, bool $active ) { + private function update_config_file( string $base_dir, Snippet $snippet, bool $remove = false ) { $config_file_path = trailingslashit( $base_dir ) . 'index.php'; $active_snippets = $this->load_config_file( $config_file_path ); - if ( $active ) { - $active_snippets[ $snippet->id ] = $snippet->get_fields(); - } else { + if ( $remove ) { unset( $active_snippets[ $snippet->id ] ); + } else { + $active_snippets[ $snippet->id ] = $snippet->get_fields(); } $this->save_config_file( $config_file_path, $active_snippets ); diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 34d2c787..39565c59 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -733,6 +733,10 @@ function execute_active_snippets_from_flat_files(): bool { continue; } + if ( ! $snippet['active'] ) { + continue; + } + // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. if ( 'single-use' === $snippet['scope'] ) { $active_shared_ids = get_option( 'active_shared_network_snippets', array() ); From 1d99420c70a43658f380f89c832540086f997161 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 22:28:45 +0200 Subject: [PATCH 017/106] wip --- src/php/class-snippet-files.php | 37 ++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 3aa97f7a..3219540e 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -24,6 +24,7 @@ public function register_hooks() { add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); + add_action( 'updated_option', [ $this, 'sync_active_shared_network_snippets' ], 10, 3 ); } private function ensure_filesystem() { @@ -107,10 +108,20 @@ public function deactivate_snippet( int $snippet_id, bool $network ) { } /** - * Returns the base directory path for a given table. + * Returns the base directory path for a given context. */ - public static function get_base_dir( string $table, string $snippet_type ) { - return WP_CONTENT_DIR . '/code-snippets/' . $table . '/' . $snippet_type; + public static function get_base_dir( string $table = '', string $snippet_type = '' ) { + $base_dir = WP_CONTENT_DIR . '/code-snippets'; + + if ( ! empty( $table ) ) { + $base_dir .= '/' . $table; + } + + if ( ! empty( $snippet_type ) ) { + $base_dir .= '/' . $snippet_type; + } + + return $base_dir; } /** @@ -164,8 +175,8 @@ private function load_config_file( string $config_file_path ) { * Saves the index.php file via WP_Filesystem. */ private function save_config_file( string $config_file_path, array $active_snippets ) { - $index_content = "fs->put_contents( $config_file_path, $index_content, FS_CHMOD_FILE ); + $file_content = "fs->put_contents( $config_file_path, $file_content, FS_CHMOD_FILE ); } /** @@ -183,4 +194,20 @@ private function update_config_file( string $base_dir, Snippet $snippet, bool $r $this->save_config_file( $config_file_path, $active_snippets ); } + + public function sync_active_shared_network_snippets( $option, $old_value, $value ) { + if ( 'active_shared_network_snippets' !== $option ) { + return; + } + + $table = code_snippets()->db->get_table_name(); + $base_dir = self::get_base_dir( $table ); + + $this->maybe_create_directory( $base_dir ); + + $file_path = trailingslashit( $base_dir ) . 'active-shared-network-snippets.php'; + $file_content = "fs->put_contents( $file_path, $file_content, FS_CHMOD_FILE ); + } } From 918e7697737b95c49be29342762cbc517917c455 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 23:25:38 +0200 Subject: [PATCH 018/106] wip --- src/php/class-snippet-files.php | 52 ++++++++++++++++++++++++++++++--- src/php/snippet-ops.php | 37 +++++------------------ 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 3219540e..4fc0bab0 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -77,6 +77,8 @@ public function delete_snippet( Snippet $snippet, bool $network ) { } public function activate_snippet( Snippet $snippet, bool $network ) { + $snippet = get_snippet( $snippet->id, $network ); + $snippet_type = $snippet->get_type(); if ( ! $this->should_handle_snippet( $snippet_type ) ) { return; @@ -182,14 +184,14 @@ private function save_config_file( string $config_file_path, array $active_snipp /** * Updates the index.php file with snippet config. */ - private function update_config_file( string $base_dir, Snippet $snippet, bool $remove = false ) { + private function update_config_file( string $base_dir, Snippet $snippet, ?bool $remove = false ) { $config_file_path = trailingslashit( $base_dir ) . 'index.php'; $active_snippets = $this->load_config_file( $config_file_path ); - if ( $remove ) { - unset( $active_snippets[ $snippet->id ] ); - } else { + if ( ! $remove ) { $active_snippets[ $snippet->id ] = $snippet->get_fields(); + } else { + unset( $active_snippets[ $snippet->id ] ); } $this->save_config_file( $config_file_path, $active_snippets ); @@ -210,4 +212,46 @@ public function sync_active_shared_network_snippets( $option, $old_value, $value $this->fs->put_contents( $file_path, $file_content, FS_CHMOD_FILE ); } + + public static function get_active_snippets_from_flat_files() { + $snippets = []; + + $table = code_snippets()->db->get_table_name(); + $base_dir = self::get_base_dir( $table, 'php' ); + $snippets_file_path = $base_dir . '/index.php'; + + if ( is_file( $snippets_file_path ) ) { + $site_snippets = is_file( $snippets_file_path ) ? require $snippets_file_path : []; + + $snippets[ $table ] = array_filter( + $site_snippets, + function ( $snippet ) { + return $snippet['active']; + } + ); + } + + if ( is_multisite() ) { + $root_base_dir = self::get_base_dir( $table ); + $active_shared_ids_file_path = $root_base_dir . '/active-shared-network-snippets.php'; + + $active_shared_ids = is_file( $active_shared_ids_file_path ) ? require $active_shared_ids_file_path : []; + $ms_table = code_snippets()->db->get_table_name( true ); + $ms_base_dir = self::get_base_dir( $ms_table, 'php' ); + $ms_snippets_file_path = $ms_base_dir . '/index.php'; + + if ( is_file( $ms_snippets_file_path ) ) { + $ms_snippets = is_file( $ms_snippets_file_path ) ? require $ms_snippets_file_path : []; + + $snippets[ $ms_table ] = array_filter( + $ms_snippets, + function ( $snippet ) use ( $active_shared_ids ) { + return $snippet['active'] || in_array( intval( $snippet['id'] ), $active_shared_ids, true ); + } + ); + } + } + + return $snippets; + } } diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 39565c59..2eab3c94 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -675,12 +675,6 @@ function execute_active_snippets(): bool { return true; } -/** - * Execute the active snippets from the flat files. - * Read-write-execute operation. - * - * @return bool true on success, false on failure. - */ function execute_active_snippets_from_flat_files(): bool { global $wpdb; @@ -691,8 +685,8 @@ function execute_active_snippets_from_flat_files(): bool { } $db = code_snippets()->db; - $tables = $db->get_active_tables(); $scopes = array( 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ); + $data = Snippet_Files::get_active_snippets_from_flat_files(); // Detect if a snippet is currently being edited, and if so, spare it from execution. $edit_id = 0; @@ -713,29 +707,14 @@ function execute_active_snippets_from_flat_files(): bool { } } - foreach ( $tables as $table_name ) { + foreach ( $data as $table_name => $active_snippets ) { $base_dir = Snippet_Files::get_base_dir( $table_name, 'php' ); + $active_snippets = cs_sort_snippets_by_priority( $active_snippets ); - if ( ! is_dir( $base_dir ) ) { - continue; - } - - $active_snippets_file_path = $base_dir . '/index.php'; - if ( ! is_file( $active_snippets_file_path ) ) { - continue; - } - - $active_snippets = require $active_snippets_file_path; - $sorted_snippets = cs_sort_snippets_by_priority( $active_snippets ); - - foreach ( $sorted_snippets as $snippet_id => $snippet ) { - if ( ! in_array( $snippet['scope'], $scopes, true ) ) { - continue; - } - - if ( ! $snippet['active'] ) { - continue; - } + // Loop through the returned snippets and execute the PHP code. + foreach ( $active_snippets as $snippet ) { + $snippet_id = intval( $snippet['id'] ); + $code = $snippet['code']; // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. if ( 'single-use' === $snippet['scope'] ) { @@ -761,9 +740,9 @@ function execute_active_snippets_from_flat_files(): bool { } } - $file = $base_dir . '/' . $snippet_id . '.php'; if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) && ! ( $edit_id === $snippet_id && $table_name === $edit_table ) ) { + $file = $base_dir . '/' . $snippet_id . '.php'; execute_snippet_from_flat_file( $file, $snippet_id ); } } From 737b3cfe7d6230b2c25417bb006057094ad6166c Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 23:37:42 +0200 Subject: [PATCH 019/106] wip --- src/php/class-snippet-files.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/php/class-snippet-files.php b/src/php/class-snippet-files.php index 4fc0bab0..9bb7bdd6 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/class-snippet-files.php @@ -232,10 +232,6 @@ function ( $snippet ) { } if ( is_multisite() ) { - $root_base_dir = self::get_base_dir( $table ); - $active_shared_ids_file_path = $root_base_dir . '/active-shared-network-snippets.php'; - - $active_shared_ids = is_file( $active_shared_ids_file_path ) ? require $active_shared_ids_file_path : []; $ms_table = code_snippets()->db->get_table_name( true ); $ms_base_dir = self::get_base_dir( $ms_table, 'php' ); $ms_snippets_file_path = $ms_base_dir . '/index.php'; @@ -243,6 +239,10 @@ function ( $snippet ) { if ( is_file( $ms_snippets_file_path ) ) { $ms_snippets = is_file( $ms_snippets_file_path ) ? require $ms_snippets_file_path : []; + $root_base_dir = self::get_base_dir( $table ); + $active_shared_ids_file_path = $root_base_dir . '/active-shared-network-snippets.php'; + $active_shared_ids = is_file( $active_shared_ids_file_path ) ? require $active_shared_ids_file_path : []; + $snippets[ $ms_table ] = array_filter( $ms_snippets, function ( $snippet ) use ( $active_shared_ids ) { From 20ec1896523dd192ab305bba28ec62896685e442 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 23:41:08 +0200 Subject: [PATCH 020/106] wip --- src/php/class-db.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/php/class-db.php b/src/php/class-db.php index a0b50782..64ea5f47 100644 --- a/src/php/class-db.php +++ b/src/php/class-db.php @@ -283,14 +283,4 @@ function ( $snippet ) use ( $active_shared_ids ) { return $active_snippets; } - - public function get_active_tables(): array { - $active_tables = array( $this->table ); - - if ( is_multisite() ) { - $active_tables[] = $this->ms_table; - } - - return $active_tables; - } } From 04e706d756eeb215921d38c830a56663efe22003 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Wed, 2 Jul 2025 23:46:21 +0200 Subject: [PATCH 021/106] wip --- src/php/snippet-ops.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 2eab3c94..8fc90ff2 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -685,7 +685,7 @@ function execute_active_snippets_from_flat_files(): bool { } $db = code_snippets()->db; - $scopes = array( 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ); + $scopes = [ 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ]; $data = Snippet_Files::get_active_snippets_from_flat_files(); // Detect if a snippet is currently being edited, and if so, spare it from execution. @@ -713,8 +713,11 @@ function execute_active_snippets_from_flat_files(): bool { // Loop through the returned snippets and execute the PHP code. foreach ( $active_snippets as $snippet ) { + if ( ! in_array( $snippet['scope'], $scopes, true ) ) { + continue; + } + $snippet_id = intval( $snippet['id'] ); - $code = $snippet['code']; // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. if ( 'single-use' === $snippet['scope'] ) { From 890481ae74afc246e4b6debe10c3a784d02f089d Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sat, 5 Jul 2025 23:40:40 +0200 Subject: [PATCH 022/106] wip --- src/php/class-plugin.php | 9 +- .../classes/class-file-system-adapter.php | 35 ++++++++ .../classes}/class-snippet-files.php | 86 +++++++------------ .../files/handlers/html-snippet-handler.php | 17 ++++ .../files/handlers/php-snippet-handler.php | 17 ++++ .../interfaces/interface-file-system.php | 11 +++ .../interfaces/interface-snippet-handler.php | 9 ++ src/php/files/load.php | 9 ++ src/php/files/registry.php | 47 ++++++++++ 9 files changed, 183 insertions(+), 57 deletions(-) create mode 100644 src/php/files/classes/class-file-system-adapter.php rename src/php/{ => files/classes}/class-snippet-files.php (78%) create mode 100644 src/php/files/handlers/html-snippet-handler.php create mode 100644 src/php/files/handlers/php-snippet-handler.php create mode 100644 src/php/files/interfaces/interface-file-system.php create mode 100644 src/php/files/interfaces/interface-snippet-handler.php create mode 100644 src/php/files/load.php create mode 100644 src/php/files/registry.php diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index fda65d6f..c09edbb4 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -120,8 +120,13 @@ public function load_plugin() { require_once $includes_path . '/cloud/list-table-shared-ops.php'; // Snippet files. - require_once $includes_path . '/class-snippet-files.php'; - ( new Snippet_Files() )->init(); + require_once $includes_path . '/files/load.php'; + $registry = new Snippet_Handler_Registry( [ + 'php' => new Php_Snippet_Handler(), + 'html' => new Html_Snippet_Handler(), + ] ); + $fs = new WordPress_Filesystem_Adapter(); + ( new Snippet_Files( $registry, $fs ) )->register_hooks(); $this->active_snippets = new Active_Snippets(); $this->front_end = new Front_End(); diff --git a/src/php/files/classes/class-file-system-adapter.php b/src/php/files/classes/class-file-system-adapter.php new file mode 100644 index 00000000..b22308e3 --- /dev/null +++ b/src/php/files/classes/class-file-system-adapter.php @@ -0,0 +1,35 @@ +fs = $wp_filesystem; + } + + public function put_contents( string $path, string $contents, $chmod ) { + return $this->fs->put_contents( $path, $contents, $chmod ); + } + + public function exists( string $path ): bool { + return $this->fs->exists( $path ); + } + + public function delete( string $path ): bool { + return $this->fs->delete( $path ); + } + + public function is_dir( string $path ): bool { + return $this->fs->is_dir( $path ); + } + + public function mkdir( string $path, $chmod ) { + return $this->fs->mkdir( $path, $chmod ); + } +} diff --git a/src/php/class-snippet-files.php b/src/php/files/classes/class-snippet-files.php similarity index 78% rename from src/php/class-snippet-files.php rename to src/php/files/classes/class-snippet-files.php index 9bb7bdd6..a7a0df61 100644 --- a/src/php/class-snippet-files.php +++ b/src/php/files/classes/class-snippet-files.php @@ -4,19 +4,17 @@ class Snippet_Files { - /** - * Holds the WP_Filesystem instance. - * - * @var \WP_Filesystem_Base - */ - private $fs; + private Snippet_Handler_Registry $handler_registry; - const TYPES_TO_HANDLE = [ 'php', 'html' ]; + private File_System_Interface $fs; - public function init() { - $this->ensure_filesystem(); - $this->register_hooks(); - } + public function __construct( + Snippet_Handler_Registry $handler_registry, + File_System_Interface $fs + ) { + $this->handler_registry = $handler_registry; + $this->fs = $fs; + } public function register_hooks() { add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); @@ -27,48 +25,36 @@ public function register_hooks() { add_action( 'updated_option', [ $this, 'sync_active_shared_network_snippets' ], 10, 3 ); } - private function ensure_filesystem() { - if ( ! $this->fs ) { - if ( ! function_exists( 'WP_Filesystem' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - } - - WP_Filesystem(); - - global $wp_filesystem; - - $this->fs = $wp_filesystem; - } - } - - private function should_handle_snippet( string $snippet_type ) { - return in_array( $snippet_type, self::TYPES_TO_HANDLE, true ); - } - public function handle_snippet( Snippet $snippet, string $table ) { $snippet_type = $snippet->get_type(); - if ( ! $this->should_handle_snippet( $snippet_type ) ) { + $handler = $this->handler_registry->get_handler( $snippet_type ); + + if ( ! $handler ) { return; } - $base_dir = self::get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->maybe_create_directory( $base_dir ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); - $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); + $contents = $handler->wrap_code( $snippet->code ); + + $this->fs->put_contents( $file_path, $contents, FS_CHMOD_FILE ); $this->update_config_file( $base_dir, $snippet ); } public function delete_snippet( Snippet $snippet, bool $network ) { $snippet_type = $snippet->get_type(); - if ( ! $this->should_handle_snippet( $snippet_type ) ) { + $handler = $this->handler_registry->get_handler( $snippet_type ); + + if ( ! $handler ) { return; } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = self::get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); $this->delete_file( $file_path ); @@ -78,19 +64,23 @@ public function delete_snippet( Snippet $snippet, bool $network ) { public function activate_snippet( Snippet $snippet, bool $network ) { $snippet = get_snippet( $snippet->id, $network ); - $snippet_type = $snippet->get_type(); - if ( ! $this->should_handle_snippet( $snippet_type ) ) { + $handler = $this->handler_registry->get_handler( $snippet_type ); + + if ( ! $handler ) { return; } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = self::get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->maybe_create_directory( $base_dir ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); - $this->write_snippet_file( $file_path, $snippet->code, $snippet_type ); + + $contents = $handler->wrap_code( $snippet->code ); + + $this->fs->put_contents( $file_path, $contents, FS_CHMOD_FILE ); $this->update_config_file( $base_dir, $snippet ); } @@ -98,13 +88,14 @@ public function activate_snippet( Snippet $snippet, bool $network ) { public function deactivate_snippet( int $snippet_id, bool $network ) { $snippet = get_snippet( $snippet_id, $network ); $snippet_type = $snippet->get_type(); + $handler = $this->handler_registry->get_handler( $snippet_type ); - if ( ! $this->should_handle_snippet( $snippet_type ) ) { + if ( ! $handler ) { return; } $table = code_snippets()->db->get_table_name( $network ); - $base_dir = self::get_base_dir( $table, $snippet_type ); + $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->update_config_file( $base_dir, $snippet ); } @@ -142,21 +133,6 @@ private function get_snippet_file_path( string $base_dir, int $snippet_id ) { return trailingslashit( $base_dir ) . $snippet_id . '.php'; } - /** - * Writes the snippet code to a file, with the required header. - */ - private function write_snippet_file( string $file_path, string $code, string $snippet_type ) { - $content = "\n\n"; - } - - $content .= $code; - - $this->fs->put_contents( $file_path, $content, FS_CHMOD_FILE ); - } - /** * Deletes a file if it exists. */ diff --git a/src/php/files/handlers/html-snippet-handler.php b/src/php/files/handlers/html-snippet-handler.php new file mode 100644 index 00000000..7916297d --- /dev/null +++ b/src/php/files/handlers/html-snippet-handler.php @@ -0,0 +1,17 @@ +\n\n" . $code; + } +} diff --git a/src/php/files/handlers/php-snippet-handler.php b/src/php/files/handlers/php-snippet-handler.php new file mode 100644 index 00000000..666617b0 --- /dev/null +++ b/src/php/files/handlers/php-snippet-handler.php @@ -0,0 +1,17 @@ + $handler ) { + $this->register_handler( $type, $handler ); + } + } + + /** + * Registers a handler for a snippet type. + * + * @param string $type + * @param Snippet_Type_Handler_Interface $handler + * @return void + */ + public function register_handler( string $type, Snippet_Type_Handler_Interface $handler ): void { + $this->handlers[ $type ] = $handler; + } + + /** + * Gets the handler for a snippet type. + * + * @param string $type + * + * @return Snippet_Type_Handler_Interface|null + */ + public function get_handler( string $type ): ?Snippet_Type_Handler_Interface { + if ( ! isset( $this->handlers[ $type ] ) ) { + return null; + } + + return $this->handlers[ $type ]; + } +} From 3f5eea3fb3ddeebceb6a9a4d51939ccdd02930af Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sat, 5 Jul 2025 23:44:39 +0200 Subject: [PATCH 023/106] wip --- src/php/class-plugin.php | 2 +- .../{files => flat-files}/classes/class-file-system-adapter.php | 0 src/php/{files => flat-files}/classes/class-snippet-files.php | 0 src/php/{files => flat-files}/handlers/html-snippet-handler.php | 0 src/php/{files => flat-files}/handlers/php-snippet-handler.php | 0 .../{files => flat-files}/interfaces/interface-file-system.php | 0 .../interfaces/interface-snippet-handler.php | 0 src/php/{files => flat-files}/load.php | 0 src/php/{files => flat-files}/registry.php | 0 9 files changed, 1 insertion(+), 1 deletion(-) rename src/php/{files => flat-files}/classes/class-file-system-adapter.php (100%) rename src/php/{files => flat-files}/classes/class-snippet-files.php (100%) rename src/php/{files => flat-files}/handlers/html-snippet-handler.php (100%) rename src/php/{files => flat-files}/handlers/php-snippet-handler.php (100%) rename src/php/{files => flat-files}/interfaces/interface-file-system.php (100%) rename src/php/{files => flat-files}/interfaces/interface-snippet-handler.php (100%) rename src/php/{files => flat-files}/load.php (100%) rename src/php/{files => flat-files}/registry.php (100%) diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index c09edbb4..ed07aaec 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -120,7 +120,7 @@ public function load_plugin() { require_once $includes_path . '/cloud/list-table-shared-ops.php'; // Snippet files. - require_once $includes_path . '/files/load.php'; + require_once $includes_path . '/flat-files/load.php'; $registry = new Snippet_Handler_Registry( [ 'php' => new Php_Snippet_Handler(), 'html' => new Html_Snippet_Handler(), diff --git a/src/php/files/classes/class-file-system-adapter.php b/src/php/flat-files/classes/class-file-system-adapter.php similarity index 100% rename from src/php/files/classes/class-file-system-adapter.php rename to src/php/flat-files/classes/class-file-system-adapter.php diff --git a/src/php/files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php similarity index 100% rename from src/php/files/classes/class-snippet-files.php rename to src/php/flat-files/classes/class-snippet-files.php diff --git a/src/php/files/handlers/html-snippet-handler.php b/src/php/flat-files/handlers/html-snippet-handler.php similarity index 100% rename from src/php/files/handlers/html-snippet-handler.php rename to src/php/flat-files/handlers/html-snippet-handler.php diff --git a/src/php/files/handlers/php-snippet-handler.php b/src/php/flat-files/handlers/php-snippet-handler.php similarity index 100% rename from src/php/files/handlers/php-snippet-handler.php rename to src/php/flat-files/handlers/php-snippet-handler.php diff --git a/src/php/files/interfaces/interface-file-system.php b/src/php/flat-files/interfaces/interface-file-system.php similarity index 100% rename from src/php/files/interfaces/interface-file-system.php rename to src/php/flat-files/interfaces/interface-file-system.php diff --git a/src/php/files/interfaces/interface-snippet-handler.php b/src/php/flat-files/interfaces/interface-snippet-handler.php similarity index 100% rename from src/php/files/interfaces/interface-snippet-handler.php rename to src/php/flat-files/interfaces/interface-snippet-handler.php diff --git a/src/php/files/load.php b/src/php/flat-files/load.php similarity index 100% rename from src/php/files/load.php rename to src/php/flat-files/load.php diff --git a/src/php/files/registry.php b/src/php/flat-files/registry.php similarity index 100% rename from src/php/files/registry.php rename to src/php/flat-files/registry.php From 69449a01470195e85102078a1dcfe55a3b1e9ebb Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sat, 5 Jul 2025 23:48:38 +0200 Subject: [PATCH 024/106] wip --- .../classes/class-file-system-adapter.php | 48 ++++++------ .../handlers/html-snippet-handler.php | 18 ++--- .../interfaces/interface-file-system.php | 10 +-- .../interfaces/interface-snippet-handler.php | 6 +- src/php/flat-files/registry.php | 74 +++++++++---------- 5 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/php/flat-files/classes/class-file-system-adapter.php b/src/php/flat-files/classes/class-file-system-adapter.php index b22308e3..c8085e5c 100644 --- a/src/php/flat-files/classes/class-file-system-adapter.php +++ b/src/php/flat-files/classes/class-file-system-adapter.php @@ -2,34 +2,34 @@ namespace Code_Snippets; class WordPress_Filesystem_Adapter implements File_System_Interface { - private $fs; + private $fs; - public function __construct() { - if ( ! function_exists( 'WP_Filesystem' ) ) { - require_once ABSPATH . 'wp-admin/includes/file.php'; - } - WP_Filesystem(); - global $wp_filesystem; - $this->fs = $wp_filesystem; - } + public function __construct() { + if ( ! function_exists( 'WP_Filesystem' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + WP_Filesystem(); + global $wp_filesystem; + $this->fs = $wp_filesystem; + } - public function put_contents( string $path, string $contents, $chmod ) { - return $this->fs->put_contents( $path, $contents, $chmod ); - } + public function put_contents( string $path, string $contents, $chmod ) { + return $this->fs->put_contents( $path, $contents, $chmod ); + } - public function exists( string $path ): bool { - return $this->fs->exists( $path ); - } + public function exists( string $path ): bool { + return $this->fs->exists( $path ); + } - public function delete( string $path ): bool { - return $this->fs->delete( $path ); - } + public function delete( string $path ): bool { + return $this->fs->delete( $path ); + } - public function is_dir( string $path ): bool { - return $this->fs->is_dir( $path ); - } + public function is_dir( string $path ): bool { + return $this->fs->is_dir( $path ); + } - public function mkdir( string $path, $chmod ) { - return $this->fs->mkdir( $path, $chmod ); - } + public function mkdir( string $path, $chmod ) { + return $this->fs->mkdir( $path, $chmod ); + } } diff --git a/src/php/flat-files/handlers/html-snippet-handler.php b/src/php/flat-files/handlers/html-snippet-handler.php index 7916297d..d7a4446a 100644 --- a/src/php/flat-files/handlers/html-snippet-handler.php +++ b/src/php/flat-files/handlers/html-snippet-handler.php @@ -3,15 +3,15 @@ namespace Code_Snippets; class Html_Snippet_Handler implements Snippet_Type_Handler_Interface { - public function get_file_extension(): string { - return 'php'; - } + public function get_file_extension(): string { + return 'php'; + } - public function get_dir_name(): string { - return 'html'; - } + public function get_dir_name(): string { + return 'html'; + } - public function wrap_code( string $code ): string { - return "\n\n" . $code; - } + public function wrap_code( string $code ): string { + return "\n\n" . $code; + } } diff --git a/src/php/flat-files/interfaces/interface-file-system.php b/src/php/flat-files/interfaces/interface-file-system.php index 3a8cc7a3..38ab5bd0 100644 --- a/src/php/flat-files/interfaces/interface-file-system.php +++ b/src/php/flat-files/interfaces/interface-file-system.php @@ -3,9 +3,9 @@ namespace Code_Snippets; interface File_System_Interface { - public function put_contents( string $path, string $contents, $chmod ); - public function exists( string $path ): bool; - public function delete( string $path ): bool; - public function is_dir( string $path ): bool; - public function mkdir( string $path, $chmod ); + public function put_contents( string $path, string $contents, $chmod ); + public function exists( string $path ): bool; + public function delete( string $path ): bool; + public function is_dir( string $path ): bool; + public function mkdir( string $path, $chmod ); } diff --git a/src/php/flat-files/interfaces/interface-snippet-handler.php b/src/php/flat-files/interfaces/interface-snippet-handler.php index fdcbb6b8..4ff024cb 100644 --- a/src/php/flat-files/interfaces/interface-snippet-handler.php +++ b/src/php/flat-files/interfaces/interface-snippet-handler.php @@ -3,7 +3,7 @@ namespace Code_Snippets; interface Snippet_Type_Handler_Interface { - public function get_file_extension(): string; - public function get_dir_name(): string; - public function wrap_code( string $code ): string; + public function get_file_extension(): string; + public function get_dir_name(): string; + public function wrap_code( string $code ): string; } diff --git a/src/php/flat-files/registry.php b/src/php/flat-files/registry.php index a02b589b..4fce503d 100644 --- a/src/php/flat-files/registry.php +++ b/src/php/flat-files/registry.php @@ -3,45 +3,45 @@ namespace Code_Snippets; class Snippet_Handler_Registry { - /** - * @var Snippet_Type_Handler_Interface[] - */ - private array $handlers = []; + /** + * @var Snippet_Type_Handler_Interface[] + */ + private array $handlers = []; - /** - * Constructor - * - * @param Snippet_Type_Handler_Interface[] $handlers - */ - public function __construct( array $handlers ) { - foreach ( $handlers as $type => $handler ) { - $this->register_handler( $type, $handler ); - } - } + /** + * Constructor + * + * @param Snippet_Type_Handler_Interface[] $handlers + */ + public function __construct( array $handlers ) { + foreach ( $handlers as $type => $handler ) { + $this->register_handler( $type, $handler ); + } + } - /** - * Registers a handler for a snippet type. - * - * @param string $type - * @param Snippet_Type_Handler_Interface $handler - * @return void - */ - public function register_handler( string $type, Snippet_Type_Handler_Interface $handler ): void { - $this->handlers[ $type ] = $handler; - } + /** + * Registers a handler for a snippet type. + * + * @param string $type + * @param Snippet_Type_Handler_Interface $handler + * @return void + */ + public function register_handler( string $type, Snippet_Type_Handler_Interface $handler ): void { + $this->handlers[ $type ] = $handler; + } - /** - * Gets the handler for a snippet type. - * - * @param string $type - * - * @return Snippet_Type_Handler_Interface|null - */ - public function get_handler( string $type ): ?Snippet_Type_Handler_Interface { - if ( ! isset( $this->handlers[ $type ] ) ) { - return null; - } + /** + * Gets the handler for a snippet type. + * + * @param string $type + * + * @return Snippet_Type_Handler_Interface|null + */ + public function get_handler( string $type ): ?Snippet_Type_Handler_Interface { + if ( ! isset( $this->handlers[ $type ] ) ) { + return null; + } - return $this->handlers[ $type ]; - } + return $this->handlers[ $type ]; + } } From 91b754696d144c0c119b89482aa8dfcecb6bd342 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sat, 5 Jul 2025 23:49:37 +0200 Subject: [PATCH 025/106] wip --- src/php/flat-files/classes/class-snippet-files.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index a7a0df61..9750d2a2 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -8,13 +8,13 @@ class Snippet_Files { private File_System_Interface $fs; - public function __construct( - Snippet_Handler_Registry $handler_registry, + public function __construct( + Snippet_Handler_Registry $handler_registry, File_System_Interface $fs - ) { - $this->handler_registry = $handler_registry; - $this->fs = $fs; - } + ) { + $this->handler_registry = $handler_registry; + $this->fs = $fs; + } public function register_hooks() { add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); From 0029e0f0dde5f386acaf1b1695f4dc0a8baa0f89 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sat, 5 Jul 2025 23:55:22 +0200 Subject: [PATCH 026/106] wip --- src/php/flat-files/classes/class-snippet-files.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index 9750d2a2..add308ad 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -36,7 +36,7 @@ public function handle_snippet( Snippet $snippet, string $table ) { $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->maybe_create_directory( $base_dir ); - $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); + $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); $contents = $handler->wrap_code( $snippet->code ); @@ -56,7 +56,7 @@ public function delete_snippet( Snippet $snippet, bool $network ) { $table = code_snippets()->db->get_table_name( $network ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); - $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); + $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); $this->delete_file( $file_path ); $this->update_config_file( $base_dir, $snippet, true ); @@ -76,7 +76,7 @@ public function activate_snippet( Snippet $snippet, bool $network ) { $this->maybe_create_directory( $base_dir ); - $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id ); + $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); $contents = $handler->wrap_code( $snippet->code ); @@ -129,8 +129,8 @@ private function maybe_create_directory( string $dir ) { /** * Returns the path to the snippet PHP file. */ - private function get_snippet_file_path( string $base_dir, int $snippet_id ) { - return trailingslashit( $base_dir ) . $snippet_id . '.php'; + private function get_snippet_file_path( string $base_dir, int $snippet_id, string $ext ) { + return trailingslashit( $base_dir ) . $snippet_id . '.' . $ext; } /** From 1a7789e9792923dba658880188f7b00d961c10a6 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 00:07:32 +0200 Subject: [PATCH 027/106] wip --- src/php/class-plugin.php | 7 ++- .../classes/class-config-repository.php | 41 +++++++++++++ .../classes/class-snippet-files.php | 57 +++---------------- .../interface-config-repository.php | 9 +++ src/php/flat-files/load.php | 1 + 5 files changed, 66 insertions(+), 49 deletions(-) create mode 100644 src/php/flat-files/classes/class-config-repository.php create mode 100644 src/php/flat-files/interfaces/interface-config-repository.php diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index ed07aaec..9366a5f8 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -121,12 +121,17 @@ public function load_plugin() { // Snippet files. require_once $includes_path . '/flat-files/load.php'; + $registry = new Snippet_Handler_Registry( [ 'php' => new Php_Snippet_Handler(), 'html' => new Html_Snippet_Handler(), ] ); + $fs = new WordPress_Filesystem_Adapter(); - ( new Snippet_Files( $registry, $fs ) )->register_hooks(); + + $config_repo = new Snippet_Config_Repository( $fs ); + + ( new Snippet_Files( $registry, $fs, $config_repo ) )->register_hooks(); $this->active_snippets = new Active_Snippets(); $this->front_end = new Front_End(); diff --git a/src/php/flat-files/classes/class-config-repository.php b/src/php/flat-files/classes/class-config-repository.php new file mode 100644 index 00000000..bcf48535 --- /dev/null +++ b/src/php/flat-files/classes/class-config-repository.php @@ -0,0 +1,41 @@ +fs = $fs; + } + + public function load( string $base_dir ): array { + $config_file_path = trailingslashit( $base_dir ) . 'index.php'; + + return is_file( $config_file_path ) + ? require $config_file_path + : []; + } + + public function save( string $base_dir, array $active_snippets ): void { + $config_file_path = trailingslashit( $base_dir ) . 'index.php'; + + $file_content = "fs->put_contents( $config_file_path, $file_content, FS_CHMOD_FILE ); + } + + public function update( string $base_dir, Snippet $snippet, ?bool $remove = false ): void { + $active_snippets = $this->load( $base_dir ); + + if ( ! $remove ) { + $active_snippets[ $snippet->id ] = $snippet->get_fields(); + } else { + unset( $active_snippets[ $snippet->id ] ); + } + + $this->save( $base_dir, $active_snippets ); + } +} diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index add308ad..35ccaa03 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -8,12 +8,16 @@ class Snippet_Files { private File_System_Interface $fs; + private Snippet_Config_Repository_Interface $config_repo; + public function __construct( Snippet_Handler_Registry $handler_registry, - File_System_Interface $fs + File_System_Interface $fs, + Snippet_Config_Repository_Interface $config_repo ) { $this->handler_registry = $handler_registry; $this->fs = $fs; + $this->config_repo = $config_repo; } public function register_hooks() { @@ -42,7 +46,7 @@ public function handle_snippet( Snippet $snippet, string $table ) { $this->fs->put_contents( $file_path, $contents, FS_CHMOD_FILE ); - $this->update_config_file( $base_dir, $snippet ); + $this->config_repo->update( $base_dir, $snippet ); } public function delete_snippet( Snippet $snippet, bool $network ) { @@ -59,7 +63,7 @@ public function delete_snippet( Snippet $snippet, bool $network ) { $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); $this->delete_file( $file_path ); - $this->update_config_file( $base_dir, $snippet, true ); + $this->config_repo->update( $base_dir, $snippet, true ); } public function activate_snippet( Snippet $snippet, bool $network ) { @@ -82,7 +86,7 @@ public function activate_snippet( Snippet $snippet, bool $network ) { $this->fs->put_contents( $file_path, $contents, FS_CHMOD_FILE ); - $this->update_config_file( $base_dir, $snippet ); + $this->config_repo->update( $base_dir, $snippet ); } public function deactivate_snippet( int $snippet_id, bool $network ) { @@ -97,12 +101,9 @@ public function deactivate_snippet( int $snippet_id, bool $network ) { $table = code_snippets()->db->get_table_name( $network ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); - $this->update_config_file( $base_dir, $snippet ); + $this->config_repo->update( $base_dir, $snippet ); } - /** - * Returns the base directory path for a given context. - */ public static function get_base_dir( string $table = '', string $snippet_type = '' ) { $base_dir = WP_CONTENT_DIR . '/code-snippets'; @@ -117,62 +118,22 @@ public static function get_base_dir( string $table = '', string $snippet_type = return $base_dir; } - /** - * Creates the directory if it does not exist. - */ private function maybe_create_directory( string $dir ) { if ( ! $this->fs->is_dir( $dir ) ) { $this->fs->mkdir( $dir, FS_CHMOD_DIR ); } } - /** - * Returns the path to the snippet PHP file. - */ private function get_snippet_file_path( string $base_dir, int $snippet_id, string $ext ) { return trailingslashit( $base_dir ) . $snippet_id . '.' . $ext; } - /** - * Deletes a file if it exists. - */ private function delete_file( string $file_path ) { if ( $this->fs->exists( $file_path ) ) { $this->fs->delete( $file_path ); } } - /** - * Loads the index.php array by requiring it directly. - */ - private function load_config_file( string $config_file_path ) { - return is_file( $config_file_path ) ? require $config_file_path : []; - } - - /** - * Saves the index.php file via WP_Filesystem. - */ - private function save_config_file( string $config_file_path, array $active_snippets ) { - $file_content = "fs->put_contents( $config_file_path, $file_content, FS_CHMOD_FILE ); - } - - /** - * Updates the index.php file with snippet config. - */ - private function update_config_file( string $base_dir, Snippet $snippet, ?bool $remove = false ) { - $config_file_path = trailingslashit( $base_dir ) . 'index.php'; - $active_snippets = $this->load_config_file( $config_file_path ); - - if ( ! $remove ) { - $active_snippets[ $snippet->id ] = $snippet->get_fields(); - } else { - unset( $active_snippets[ $snippet->id ] ); - } - - $this->save_config_file( $config_file_path, $active_snippets ); - } - public function sync_active_shared_network_snippets( $option, $old_value, $value ) { if ( 'active_shared_network_snippets' !== $option ) { return; diff --git a/src/php/flat-files/interfaces/interface-config-repository.php b/src/php/flat-files/interfaces/interface-config-repository.php new file mode 100644 index 00000000..e8c3a819 --- /dev/null +++ b/src/php/flat-files/interfaces/interface-config-repository.php @@ -0,0 +1,9 @@ + Date: Sun, 6 Jul 2025 00:08:36 +0200 Subject: [PATCH 028/106] wip --- src/php/flat-files/load.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/php/flat-files/load.php b/src/php/flat-files/load.php index ea42c050..476a5113 100644 --- a/src/php/flat-files/load.php +++ b/src/php/flat-files/load.php @@ -8,3 +8,4 @@ require_once 'handlers/html-snippet-handler.php'; require_once 'classes/class-file-system-adapter.php'; require_once 'classes/class-snippet-files.php'; +require_once 'classes/class-config-repository.php'; From 87b9b2dca9ae96755e4c0951489b8579ce4958ee Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 00:27:08 +0200 Subject: [PATCH 029/106] wip --- src/php/flat-files/load.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/php/flat-files/load.php b/src/php/flat-files/load.php index 476a5113..dd87d808 100644 --- a/src/php/flat-files/load.php +++ b/src/php/flat-files/load.php @@ -1,11 +1,17 @@ Date: Sun, 6 Jul 2025 21:51:13 +0200 Subject: [PATCH 030/106] wip --- src/php/flat-files/classes/class-snippet-files.php | 13 +++++++++++++ src/php/load.php | 7 +++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index 35ccaa03..093e33fd 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -26,7 +26,10 @@ public function register_hooks() { add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); + add_action( 'updated_option', [ $this, 'sync_active_shared_network_snippets' ], 10, 3 ); + + add_filter( 'code_snippets_settings_fields', [ $this, 'add_settings_fields' ], 10, 1 ); } public function handle_snippet( Snippet $snippet, string $table ) { @@ -191,4 +194,14 @@ function ( $snippet ) use ( $active_shared_ids ) { return $snippets; } + + public function add_settings_fields( array $fields ) { + $fields['general']['enable_flat_files'] = [ + 'name' => __( 'Enable Flat Files', 'code-snippets' ), + 'type' => 'checkbox', + 'label' => __( 'Snippets will be executed from flat files instead of the database.', 'code-snippets' ), + ]; + + return $fields; + } } diff --git a/src/php/load.php b/src/php/load.php index 8700186a..220d95a5 100644 --- a/src/php/load.php +++ b/src/php/load.php @@ -65,5 +65,8 @@ function code_snippets(): Plugin { code_snippets()->load_plugin(); // Execute the snippets once the plugins are loaded. -// add_action( 'plugins_loaded', __NAMESPACE__ . '\execute_active_snippets', 1 ); -add_action( 'plugins_loaded', __NAMESPACE__ . '\execute_active_snippets_from_flat_files', 1 ); +$snippet_execution_fn = Settings\get_setting( 'general', 'enable_flat_files' ) + ? '\execute_active_snippets_from_flat_files' + : '\execute_active_snippets'; + +add_action( 'plugins_loaded', __NAMESPACE__ . $snippet_execution_fn, 1 ); From 531ee3515a14ff6f0e5723337194e9524fa6b59b Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 21:57:51 +0200 Subject: [PATCH 031/106] wip --- src/php/front-end/class-front-end.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/php/front-end/class-front-end.php b/src/php/front-end/class-front-end.php index 60429b35..258b27ec 100644 --- a/src/php/front-end/class-front-end.php +++ b/src/php/front-end/class-front-end.php @@ -245,6 +245,12 @@ protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): return $snippet->code; } + $should_execute_from_flat_files = Settings\get_setting( 'general', 'enable_flat_files' ); + + if ( ! $should_execute_from_flat_files ) { + return $this->evaluate_shortcode_from_db( $snippet, $atts ); + } + $network = DB::validate_network_param( $snippet->network ); $table_name = code_snippets()->db->get_table_name( $network ); $filepath = WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/html/' . $snippet->id . '.php'; From 0b19d6ef3ef82408ff3b47cd1eec24bcae4cc850 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 22:07:48 +0200 Subject: [PATCH 032/106] wip --- src/php/class-plugin.php | 2 +- src/php/front-end/class-front-end.php | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index 9366a5f8..bb21473a 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -134,7 +134,7 @@ public function load_plugin() { ( new Snippet_Files( $registry, $fs, $config_repo ) )->register_hooks(); $this->active_snippets = new Active_Snippets(); - $this->front_end = new Front_End(); + $this->front_end = new Front_End( $registry ); $this->cloud_api = new Cloud_API(); $upgrade = new Upgrade( $this->version, $this->db ); diff --git a/src/php/front-end/class-front-end.php b/src/php/front-end/class-front-end.php index 258b27ec..f0ddf8e1 100644 --- a/src/php/front-end/class-front-end.php +++ b/src/php/front-end/class-front-end.php @@ -33,10 +33,14 @@ class Front_End { */ const MAX_SHORTCODE_DEPTH = 5; + private Snippet_Handler_Registry $handler_registry; + /** * Class constructor */ - public function __construct() { + public function __construct( Snippet_Handler_Registry $handler_registry ) { + $this->handler_registry = $handler_registry; + add_action( 'the_posts', [ $this, 'enqueue_highlighting' ] ); add_action( 'init', [ $this, 'setup_mce_plugin' ] ); @@ -232,6 +236,20 @@ protected function convert_boolean_attribute_flags( array $atts, array $boolean_ return $atts; } + /** + * Build the file path for a snippet's flat file. + * + * @param string $table_name Table name for the snippet. + * @param Snippet $snippet Snippet object. + * + * @return string Full file path for the snippet. + */ + private function build_snippet_filepath( string $table_name, Snippet $snippet ): string { + $handler = $this->handler_registry->get_handler( $snippet->get_type() ); + + return WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/' . $handler->get_dir_name() . '/' . $snippet->id . '.' . $handler->get_file_extension(); + } + /** * Evaluate the code from a content shortcode. * @@ -253,7 +271,7 @@ protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): $network = DB::validate_network_param( $snippet->network ); $table_name = code_snippets()->db->get_table_name( $network ); - $filepath = WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/html/' . $snippet->id . '.php'; + $filepath = $this->build_snippet_filepath( $table_name, $snippet ); return file_exists( $filepath ) ? $this->evaluate_shortcode_from_flat_file( $filepath, $atts ) From ff80d08a00934d88ab6ea02b2c6561dc355a278e Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 22:10:29 +0200 Subject: [PATCH 033/106] wip --- src/php/front-end/class-front-end.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/php/front-end/class-front-end.php b/src/php/front-end/class-front-end.php index f0ddf8e1..66d00e05 100644 --- a/src/php/front-end/class-front-end.php +++ b/src/php/front-end/class-front-end.php @@ -244,7 +244,7 @@ protected function convert_boolean_attribute_flags( array $atts, array $boolean_ * * @return string Full file path for the snippet. */ - private function build_snippet_filepath( string $table_name, Snippet $snippet ): string { + private function build_snippet_flat_file_path( string $table_name, Snippet $snippet ): string { $handler = $this->handler_registry->get_handler( $snippet->get_type() ); return WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/' . $handler->get_dir_name() . '/' . $snippet->id . '.' . $handler->get_file_extension(); @@ -271,7 +271,7 @@ protected function evaluate_shortcode_content( Snippet $snippet, array $atts ): $network = DB::validate_network_param( $snippet->network ); $table_name = code_snippets()->db->get_table_name( $network ); - $filepath = $this->build_snippet_filepath( $table_name, $snippet ); + $filepath = $this->build_snippet_flat_file_path( $table_name, $snippet ); return file_exists( $filepath ) ? $this->evaluate_shortcode_from_flat_file( $filepath, $atts ) From b533854ab4f5ae9501298e858fcff31117b100f2 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 22:13:32 +0200 Subject: [PATCH 034/106] wip --- .../flat-files/classes/class-snippet-files.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index 093e33fd..8765780f 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -21,13 +21,15 @@ public function __construct( } public function register_hooks() { - add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); - add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); - add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); - add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); - add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); - - add_action( 'updated_option', [ $this, 'sync_active_shared_network_snippets' ], 10, 3 ); + if ( Settings\get_setting( 'general', 'enable_flat_files' ) ) { + add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); + add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); + add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); + add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); + add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); + + add_action( 'updated_option', [ $this, 'sync_active_shared_network_snippets' ], 10, 3 ); + } add_filter( 'code_snippets_settings_fields', [ $this, 'add_settings_fields' ], 10, 1 ); } From b983aa79f74cba15ecc33221815ea3155bee22c7 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 22:29:23 +0200 Subject: [PATCH 035/106] wip --- .../classes/class-file-system-adapter.php | 4 ++++ .../interfaces/interface-file-system.php | 1 + src/php/uninstall.php | 15 ++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/php/flat-files/classes/class-file-system-adapter.php b/src/php/flat-files/classes/class-file-system-adapter.php index c8085e5c..6c230f13 100644 --- a/src/php/flat-files/classes/class-file-system-adapter.php +++ b/src/php/flat-files/classes/class-file-system-adapter.php @@ -32,4 +32,8 @@ public function is_dir( string $path ): bool { public function mkdir( string $path, $chmod ) { return $this->fs->mkdir( $path, $chmod ); } + + public function rmdir( string $path, bool $recursive = false ): bool { + return $this->fs->rmdir( $path, $recursive ); + } } diff --git a/src/php/flat-files/interfaces/interface-file-system.php b/src/php/flat-files/interfaces/interface-file-system.php index 38ab5bd0..7560d7bb 100644 --- a/src/php/flat-files/interfaces/interface-file-system.php +++ b/src/php/flat-files/interfaces/interface-file-system.php @@ -8,4 +8,5 @@ public function exists( string $path ): bool; public function delete( string $path ): bool; public function is_dir( string $path ): bool; public function mkdir( string $path, $chmod ); + public function rmdir( string $path, bool $recursive = false ): bool; } diff --git a/src/php/uninstall.php b/src/php/uninstall.php index 4da58cfe..028aca5c 100644 --- a/src/php/uninstall.php +++ b/src/php/uninstall.php @@ -18,7 +18,7 @@ function complete_uninstall_enabled(): bool { $unified = false; if ( is_multisite() ) { - $menu_perms = get_site_option( 'menu_items', array() ); + $menu_perms = get_site_option( 'menu_items', [] ); $unified = empty( $menu_perms['snippets_settings'] ); } @@ -72,6 +72,17 @@ function uninstall_multisite() { delete_site_option( 'recently_activated_snippets' ); } +function delete_flat_files_directory() { + $flat_files_dir = WP_CONTENT_DIR . '/code-snippets'; + + if ( ! is_dir( $flat_files_dir ) ) { + return; + } + + $fs = new \Code_Snippets\WordPress_Filesystem_Adapter(); + $fs->rmdir( $flat_files_dir, true ); +} + /** * Uninstall the Code Snippets plugin. * @@ -85,5 +96,7 @@ function uninstall_plugin() { } else { uninstall_current_site(); } + + delete_flat_files_directory(); } } From e3a96ab1a469657e6fc392e264a7db351dbb4851 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Sun, 6 Jul 2025 22:31:19 +0200 Subject: [PATCH 036/106] wip --- src/php/flat-files/classes/class-config-repository.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/php/flat-files/classes/class-config-repository.php b/src/php/flat-files/classes/class-config-repository.php index bcf48535..2087f572 100644 --- a/src/php/flat-files/classes/class-config-repository.php +++ b/src/php/flat-files/classes/class-config-repository.php @@ -3,6 +3,8 @@ class Snippet_Config_Repository implements Snippet_Config_Repository_Interface { + const CONFIG_FILE_NAME = 'index.php'; + private File_System_Interface $fs; public function __construct( File_System_Interface $fs ) { @@ -10,7 +12,7 @@ public function __construct( File_System_Interface $fs ) { } public function load( string $base_dir ): array { - $config_file_path = trailingslashit( $base_dir ) . 'index.php'; + $config_file_path = trailingslashit( $base_dir ) . static::CONFIG_FILE_NAME; return is_file( $config_file_path ) ? require $config_file_path @@ -18,7 +20,7 @@ public function load( string $base_dir ): array { } public function save( string $base_dir, array $active_snippets ): void { - $config_file_path = trailingslashit( $base_dir ) . 'index.php'; + $config_file_path = trailingslashit( $base_dir ) . static::CONFIG_FILE_NAME; $file_content = " Date: Sun, 6 Jul 2025 23:16:43 +0200 Subject: [PATCH 037/106] wip --- .../classes/class-snippet-files.php | 25 +++++++++++++++++++ src/php/settings/settings.php | 2 ++ 2 files changed, 27 insertions(+) diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index 8765780f..18a68176 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -32,6 +32,7 @@ public function register_hooks() { } add_filter( 'code_snippets_settings_fields', [ $this, 'add_settings_fields' ], 10, 1 ); + add_action( 'code_snippets/settings_updated', [ $this, 'create_all_flat_files' ], 10, 2 ); } public function handle_snippet( Snippet $snippet, string $table ) { @@ -206,4 +207,28 @@ public function add_settings_fields( array $fields ) { return $fields; } + + public function create_all_flat_files( array $settings, array $input ) { + if ( ! isset( $settings['general']['enable_flat_files'] ) ) { + return; + } + + if ( ! $settings['general']['enable_flat_files'] ) { + return; + } + + $db = code_snippets()->db; + $data = $db->fetch_active_snippets( Snippet::get_all_scopes() ); + + if ( empty( $data ) ) { + return; + } + + foreach ( $data as $table_name => $active_snippets ) { + foreach ( $active_snippets as $snippet ) { + $snippet_obj = get_snippet( $snippet['id'], $table_name === $db->ms_table ); + $this->handle_snippet( $snippet_obj, $table_name ); + } + } + } } diff --git a/src/php/settings/settings.php b/src/php/settings/settings.php index cf94f8b9..e3565b46 100644 --- a/src/php/settings/settings.php +++ b/src/php/settings/settings.php @@ -330,6 +330,8 @@ function sanitize_settings( array $input ): array { __( 'Settings saved.', 'code-snippets' ), 'updated' ); + + do_action( 'code_snippets/settings_updated', $settings, $input ); } return $settings; From 07f542bee02eda5b59fe8c4d2a71b1c3aa57bc1c Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Mon, 7 Jul 2025 22:04:13 +0200 Subject: [PATCH 038/106] wip --- .../classes/class-file-system-adapter.php | 8 ++++ .../classes/class-snippet-files.php | 44 +++++++++++-------- .../interfaces/interface-file-system.php | 2 + src/php/snippet-ops.php | 5 +-- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/php/flat-files/classes/class-file-system-adapter.php b/src/php/flat-files/classes/class-file-system-adapter.php index 6c230f13..c17cef28 100644 --- a/src/php/flat-files/classes/class-file-system-adapter.php +++ b/src/php/flat-files/classes/class-file-system-adapter.php @@ -36,4 +36,12 @@ public function mkdir( string $path, $chmod ) { public function rmdir( string $path, bool $recursive = false ): bool { return $this->fs->rmdir( $path, $recursive ); } + + public function chmod( string $path, $chmod ): bool { + return $this->fs->chmod( $path, $chmod ); + } + + public function is_writable( string $path ): bool { + return $this->fs->is_writable( $path ); + } } diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index 18a68176..41f7b280 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -20,7 +20,11 @@ public function __construct( $this->config_repo = $config_repo; } - public function register_hooks() { + public function register_hooks(): void { + if ( ! $this->fs->is_writable( WP_CONTENT_DIR ) ) { + return; + } + if ( Settings\get_setting( 'general', 'enable_flat_files' ) ) { add_action( 'code_snippets/create_snippet', [ $this, 'handle_snippet' ], 10, 2 ); add_action( 'code_snippets/update_snippet', [ $this, 'handle_snippet' ], 10, 2 ); @@ -35,7 +39,7 @@ public function register_hooks() { add_action( 'code_snippets/settings_updated', [ $this, 'create_all_flat_files' ], 10, 2 ); } - public function handle_snippet( Snippet $snippet, string $table ) { + public function handle_snippet( Snippet $snippet, string $table ): void { $snippet_type = $snippet->get_type(); $handler = $this->handler_registry->get_handler( $snippet_type ); @@ -55,7 +59,7 @@ public function handle_snippet( Snippet $snippet, string $table ) { $this->config_repo->update( $base_dir, $snippet ); } - public function delete_snippet( Snippet $snippet, bool $network ) { + public function delete_snippet( Snippet $snippet, bool $network ): void { $snippet_type = $snippet->get_type(); $handler = $this->handler_registry->get_handler( $snippet_type ); @@ -72,7 +76,7 @@ public function delete_snippet( Snippet $snippet, bool $network ) { $this->config_repo->update( $base_dir, $snippet, true ); } - public function activate_snippet( Snippet $snippet, bool $network ) { + public function activate_snippet( Snippet $snippet, bool $network ): void { $snippet = get_snippet( $snippet->id, $network ); $snippet_type = $snippet->get_type(); $handler = $this->handler_registry->get_handler( $snippet_type ); @@ -95,7 +99,7 @@ public function activate_snippet( Snippet $snippet, bool $network ) { $this->config_repo->update( $base_dir, $snippet ); } - public function deactivate_snippet( int $snippet_id, bool $network ) { + public function deactivate_snippet( int $snippet_id, bool $network ): void { $snippet = get_snippet( $snippet_id, $network ); $snippet_type = $snippet->get_type(); $handler = $this->handler_registry->get_handler( $snippet_type ); @@ -110,7 +114,7 @@ public function deactivate_snippet( int $snippet_id, bool $network ) { $this->config_repo->update( $base_dir, $snippet ); } - public static function get_base_dir( string $table = '', string $snippet_type = '' ) { + public static function get_base_dir( string $table = '', string $snippet_type = '' ): string { $base_dir = WP_CONTENT_DIR . '/code-snippets'; if ( ! empty( $table ) ) { @@ -124,23 +128,27 @@ public static function get_base_dir( string $table = '', string $snippet_type = return $base_dir; } - private function maybe_create_directory( string $dir ) { + private function maybe_create_directory( string $dir ): void { if ( ! $this->fs->is_dir( $dir ) ) { - $this->fs->mkdir( $dir, FS_CHMOD_DIR ); + $result = wp_mkdir_p( $dir ); + + if ( $result ) { + $this->fs->chmod( $dir, FS_CHMOD_DIR ); + } } } - private function get_snippet_file_path( string $base_dir, int $snippet_id, string $ext ) { + private function get_snippet_file_path( string $base_dir, int $snippet_id, string $ext ): string { return trailingslashit( $base_dir ) . $snippet_id . '.' . $ext; } - private function delete_file( string $file_path ) { + private function delete_file( string $file_path ): void { if ( $this->fs->exists( $file_path ) ) { $this->fs->delete( $file_path ); } } - public function sync_active_shared_network_snippets( $option, $old_value, $value ) { + public function sync_active_shared_network_snippets( $option, $old_value, $value ): void { if ( 'active_shared_network_snippets' !== $option ) { return; } @@ -156,7 +164,7 @@ public function sync_active_shared_network_snippets( $option, $old_value, $value $this->fs->put_contents( $file_path, $file_content, FS_CHMOD_FILE ); } - public static function get_active_snippets_from_flat_files() { + public static function get_active_snippets_from_flat_files( array $scopes = [] ): array { $snippets = []; $table = code_snippets()->db->get_table_name(); @@ -168,8 +176,8 @@ public static function get_active_snippets_from_flat_files() { $snippets[ $table ] = array_filter( $site_snippets, - function ( $snippet ) { - return $snippet['active']; + function ( $snippet ) use ( $scopes ) { + return $snippet['active'] && in_array( $snippet['scope'], $scopes, true ); } ); } @@ -188,8 +196,8 @@ function ( $snippet ) { $snippets[ $ms_table ] = array_filter( $ms_snippets, - function ( $snippet ) use ( $active_shared_ids ) { - return $snippet['active'] || in_array( intval( $snippet['id'] ), $active_shared_ids, true ); + function ( $snippet ) use ( $active_shared_ids, $scopes ) { + return ( $snippet['active'] || in_array( intval( $snippet['id'] ), $active_shared_ids, true ) ) && in_array( $snippet['scope'], $scopes, true ); } ); } @@ -198,7 +206,7 @@ function ( $snippet ) use ( $active_shared_ids ) { return $snippets; } - public function add_settings_fields( array $fields ) { + public function add_settings_fields( array $fields ): array { $fields['general']['enable_flat_files'] = [ 'name' => __( 'Enable Flat Files', 'code-snippets' ), 'type' => 'checkbox', @@ -208,7 +216,7 @@ public function add_settings_fields( array $fields ) { return $fields; } - public function create_all_flat_files( array $settings, array $input ) { + public function create_all_flat_files( array $settings, array $input ): void { if ( ! isset( $settings['general']['enable_flat_files'] ) ) { return; } diff --git a/src/php/flat-files/interfaces/interface-file-system.php b/src/php/flat-files/interfaces/interface-file-system.php index 7560d7bb..71b0efe2 100644 --- a/src/php/flat-files/interfaces/interface-file-system.php +++ b/src/php/flat-files/interfaces/interface-file-system.php @@ -9,4 +9,6 @@ public function delete( string $path ): bool; public function is_dir( string $path ): bool; public function mkdir( string $path, $chmod ); public function rmdir( string $path, bool $recursive = false ): bool; + public function chmod( string $path, $chmod ): bool; + public function is_writable( string $path ): bool; } diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 8fc90ff2..9cf86b91 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -686,7 +686,7 @@ function execute_active_snippets_from_flat_files(): bool { $db = code_snippets()->db; $scopes = [ 'global', 'single-use', is_admin() ? 'admin' : 'front-end' ]; - $data = Snippet_Files::get_active_snippets_from_flat_files(); + $data = Snippet_Files::get_active_snippets_from_flat_files( $scopes ); // Detect if a snippet is currently being edited, and if so, spare it from execution. $edit_id = 0; @@ -713,9 +713,6 @@ function execute_active_snippets_from_flat_files(): bool { // Loop through the returned snippets and execute the PHP code. foreach ( $active_snippets as $snippet ) { - if ( ! in_array( $snippet['scope'], $scopes, true ) ) { - continue; - } $snippet_id = intval( $snippet['id'] ); From 19708267fe80d473b9bed8a51e860bc55c1dc534 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Mon, 7 Jul 2025 22:15:22 +0200 Subject: [PATCH 039/106] wip --- .../flat-files/classes/class-snippet-files.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index 41f7b280..0f98fa57 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -153,6 +153,10 @@ public function sync_active_shared_network_snippets( $option, $old_value, $value return; } + $this->create_active_shared_network_snippets_file( $value ); + } + + private function create_active_shared_network_snippets_file( $value ): void { $table = code_snippets()->db->get_table_name(); $base_dir = self::get_base_dir( $table ); @@ -225,6 +229,11 @@ public function create_all_flat_files( array $settings, array $input ): void { return; } + $this->create_snippet_flat_files(); + $this->create_active_shared_network_snippets_config_file(); + } + + private function create_snippet_flat_files(): void { $db = code_snippets()->db; $data = $db->fetch_active_snippets( Snippet::get_all_scopes() ); @@ -239,4 +248,11 @@ public function create_all_flat_files( array $settings, array $input ): void { } } } + + private function create_active_shared_network_snippets_config_file(): void { + $active_shared_network_snippets = get_option( 'active_shared_network_snippets' ); + if ( false !== $active_shared_network_snippets ) { + $this->create_active_shared_network_snippets_file( $active_shared_network_snippets ); + } + } } From 4cd5ee80783841433c9dc2c1b2a942b546c821ef Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Mon, 7 Jul 2025 22:24:41 +0200 Subject: [PATCH 040/106] wip --- src/php/snippet-ops.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 9cf86b91..9bdb7741 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -713,8 +713,8 @@ function execute_active_snippets_from_flat_files(): bool { // Loop through the returned snippets and execute the PHP code. foreach ( $active_snippets as $snippet ) { - $snippet_id = intval( $snippet['id'] ); + $code = $snippet['code']; // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. if ( 'single-use' === $snippet['scope'] ) { @@ -743,7 +743,7 @@ function execute_active_snippets_from_flat_files(): bool { if ( apply_filters( 'code_snippets/allow_execute_snippet', true, $snippet_id, $table_name ) && ! ( $edit_id === $snippet_id && $table_name === $edit_table ) ) { $file = $base_dir . '/' . $snippet_id . '.php'; - execute_snippet_from_flat_file( $file, $snippet_id ); + execute_snippet_from_flat_file( $code, $file, $snippet_id ); } } } @@ -759,8 +759,12 @@ function cs_sort_snippets_by_priority( array $snippets ): array { return $snippets; } -function execute_snippet_from_flat_file( $file, int $id = 0, bool $force = false ) { - if ( ! is_file( $file ) || ( ! $force && defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) ) { +function execute_snippet_from_flat_file( $code, $file, int $id = 0, bool $force = false ) { + if ( ! is_file( $file ) ) { + execute_snippet( $code, $id, $force ); + } + + if ( ! $force && defined( 'CODE_SNIPPETS_SAFE_MODE' ) && CODE_SNIPPETS_SAFE_MODE ) { return false; } From 4d84a24d1dad3ab9d40b62f71b3073ebf9a6be00 Mon Sep 17 00:00:00 2001 From: Louis Wolmarans Date: Thu, 24 Jul 2025 22:11:39 +0200 Subject: [PATCH 041/106] sync all changes from pro --- src/php/class-active-snippets.php | 35 ++- src/php/class-plugin.php | 13 +- .../classes/class-config-repository.php | 12 +- .../classes/class-file-system-adapter.php | 4 +- .../classes/class-snippet-files.php | 212 ++++++++++++++---- .../handlers/php-snippet-handler.php | 18 +- src/php/flat-files/load.php | 2 +- src/php/front-end/class-front-end.php | 10 +- src/php/snippet-ops.php | 15 +- src/php/uninstall.php | 12 +- 10 files changed, 255 insertions(+), 78 deletions(-) diff --git a/src/php/class-active-snippets.php b/src/php/class-active-snippets.php index 7c1de751..b04f04b3 100644 --- a/src/php/class-active-snippets.php +++ b/src/php/class-active-snippets.php @@ -26,8 +26,15 @@ public function __construct() { * Initialise class functions. */ public function init() { - add_action( 'wp_head', [ $this, 'load_head_content' ] ); - add_action( 'wp_footer', [ $this, 'load_footer_content' ] ); + $should_use_flat_files = Settings\get_setting( 'general', 'enable_flat_files' ); + + if ( ! $should_use_flat_files ) { + add_action( 'wp_head', [ $this, 'load_head_content' ] ); + add_action( 'wp_footer', [ $this, 'load_footer_content' ] ); + } else { + add_action( 'wp_head', [ $this, 'load_head_content_from_flat_files' ] ); + add_action( 'wp_footer', [ $this, 'load_footer_content_from_flat_files' ] ); + } } /** @@ -78,4 +85,28 @@ public function load_head_content() { public function load_footer_content() { $this->print_content_snippets( 'footer-content' ); } + + public function load_head_content_from_flat_files() { + $this->load_content_snippets_from_flat_files( 'head-content' ); + } + + public function load_footer_content_from_flat_files() { + $this->load_content_snippets_from_flat_files( 'footer-content' ); + } + + private function load_content_snippets_from_flat_files( string $scope ) { + $handler = code_snippets()->snippet_handler_registry->get_handler( 'html' ); + $dir_name = $handler->get_dir_name(); + $ext = $handler->get_file_extension(); + $snippets = Snippet_Files::get_active_snippets_from_flat_files( [ $scope ], $dir_name ); + + foreach ( $snippets as $table_name => $active_snippets ) { + $active_snippets = cs_sort_snippets_by_priority( $active_snippets ); + $base_path = Snippet_Files::get_base_dir( $table_name, $dir_name ); + + foreach ( $active_snippets as $snippet ) { + require_once $base_path . '/' . $snippet['id'] . '.' . $ext; + } + } + } } diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index bb21473a..f6f2d8f8 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -68,6 +68,13 @@ class Plugin { */ public Licensing $licensing; + /** + * Handles snippet handler registration. + * + * @var Snippet_Handler_Registry + */ + public Snippet_Handler_Registry $snippet_handler_registry; + /** * Class constructor * @@ -122,7 +129,7 @@ public function load_plugin() { // Snippet files. require_once $includes_path . '/flat-files/load.php'; - $registry = new Snippet_Handler_Registry( [ + $this->snippet_handler_registry = new Snippet_Handler_Registry( [ 'php' => new Php_Snippet_Handler(), 'html' => new Html_Snippet_Handler(), ] ); @@ -131,10 +138,10 @@ public function load_plugin() { $config_repo = new Snippet_Config_Repository( $fs ); - ( new Snippet_Files( $registry, $fs, $config_repo ) )->register_hooks(); + ( new Snippet_Files( $this->snippet_handler_registry, $fs, $config_repo ) )->register_hooks(); $this->active_snippets = new Active_Snippets(); - $this->front_end = new Front_End( $registry ); + $this->front_end = new Front_End(); $this->cloud_api = new Cloud_API(); $upgrade = new Upgrade( $this->version, $this->db ); diff --git a/src/php/flat-files/classes/class-config-repository.php b/src/php/flat-files/classes/class-config-repository.php index 2087f572..74c0388f 100644 --- a/src/php/flat-files/classes/class-config-repository.php +++ b/src/php/flat-files/classes/class-config-repository.php @@ -14,14 +14,20 @@ public function __construct( File_System_Interface $fs ) { public function load( string $base_dir ): array { $config_file_path = trailingslashit( $base_dir ) . static::CONFIG_FILE_NAME; - return is_file( $config_file_path ) - ? require $config_file_path - : []; + if ( is_file( $config_file_path ) ) { + if ( function_exists( 'opcache_invalidate' ) ) { + opcache_invalidate( $config_file_path, true ); + } + return require $config_file_path; + } + return []; } public function save( string $base_dir, array $active_snippets ): void { $config_file_path = trailingslashit( $base_dir ) . static::CONFIG_FILE_NAME; + ksort( $active_snippets ); + $file_content = "fs->exists( $path ); } - public function delete( string $path ): bool { - return $this->fs->delete( $path ); + public function delete( $file, $recursive = false, $type = false ): bool { + return $this->fs->delete( $file, $recursive, $type ); } public function is_dir( string $path ): bool { diff --git a/src/php/flat-files/classes/class-snippet-files.php b/src/php/flat-files/classes/class-snippet-files.php index 0f98fa57..284c1677 100644 --- a/src/php/flat-files/classes/class-snippet-files.php +++ b/src/php/flat-files/classes/class-snippet-files.php @@ -31,15 +31,28 @@ public function register_hooks(): void { add_action( 'code_snippets/delete_snippet', [ $this, 'delete_snippet' ], 10, 2 ); add_action( 'code_snippets/activate_snippet', [ $this, 'activate_snippet' ], 10, 2 ); add_action( 'code_snippets/deactivate_snippet', [ $this, 'deactivate_snippet' ], 10, 2 ); + add_action( 'code_snippets/activate_snippets', [ $this, 'activate_snippets' ], 10, 2 ); add_action( 'updated_option', [ $this, 'sync_active_shared_network_snippets' ], 10, 3 ); + add_action( 'add_option', [ $this, 'sync_active_shared_network_snippets_add' ], 10, 2 ); } add_filter( 'code_snippets_settings_fields', [ $this, 'add_settings_fields' ], 10, 1 ); add_action( 'code_snippets/settings_updated', [ $this, 'create_all_flat_files' ], 10, 2 ); } + public function activate_snippets( $valid_snippets, $table ): void { + foreach ( $valid_snippets as $snippet ) { + $snippet->active = true; + $this->handle_snippet( $snippet, $table ); + } + } + public function handle_snippet( Snippet $snippet, string $table ): void { + if ( 0 === $snippet->id ) { + return; + } + $snippet_type = $snippet->get_type(); $handler = $this->handler_registry->get_handler( $snippet_type ); @@ -47,6 +60,7 @@ public function handle_snippet( Snippet $snippet, string $table ): void { return; } + $table = self::get_hashed_table_name( $table ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->maybe_create_directory( $base_dir ); @@ -67,7 +81,7 @@ public function delete_snippet( Snippet $snippet, bool $network ): void { return; } - $table = code_snippets()->db->get_table_name( $network ); + $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( $network ) ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $file_path = $this->get_snippet_file_path( $base_dir, $snippet->id, $handler->get_file_extension() ); @@ -85,7 +99,7 @@ public function activate_snippet( Snippet $snippet, bool $network ): void { return; } - $table = code_snippets()->db->get_table_name( $network ); + $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( $network ) ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->maybe_create_directory( $base_dir ); @@ -108,7 +122,7 @@ public function deactivate_snippet( int $snippet_id, bool $network ): void { return; } - $table = code_snippets()->db->get_table_name( $network ); + $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( $network ) ); $base_dir = self::get_base_dir( $table, $handler->get_dir_name() ); $this->config_repo->update( $base_dir, $snippet ); @@ -128,6 +142,20 @@ public static function get_base_dir( string $table = '', string $snippet_type = return $base_dir; } + public static function get_base_url( string $table = '', string $snippet_type = '' ): string { + $base_url = WP_CONTENT_URL . '/code-snippets'; + + if ( ! empty( $table ) ) { + $base_url .= '/' . $table; + } + + if ( ! empty( $snippet_type ) ) { + $base_url .= '/' . $snippet_type; + } + + return $base_url; + } + private function maybe_create_directory( string $dir ): void { if ( ! $this->fs->is_dir( $dir ) ) { $result = wp_mkdir_p( $dir ); @@ -156,8 +184,16 @@ public function sync_active_shared_network_snippets( $option, $old_value, $value $this->create_active_shared_network_snippets_file( $value ); } + public function sync_active_shared_network_snippets_add( $option, $value ): void { + if ( 'active_shared_network_snippets' !== $option ) { + return; + } + + $this->create_active_shared_network_snippets_file( $value ); + } + private function create_active_shared_network_snippets_file( $value ): void { - $table = code_snippets()->db->get_table_name(); + $table = self::get_hashed_table_name( code_snippets()->db->get_table_name( false ) ); $base_dir = self::get_base_dir( $table ); $this->maybe_create_directory( $base_dir ); @@ -168,53 +204,104 @@ private function create_active_shared_network_snippets_file( $value ): void { $this->fs->put_contents( $file_path, $file_content, FS_CHMOD_FILE ); } - public static function get_active_snippets_from_flat_files( array $scopes = [] ): array { + public static function get_hashed_table_name( string $table ): string { + return wp_hash( $table ); + } + + public static function get_active_snippets_from_flat_files( + array $scopes = [], + $snippet_type = 'php' + ): array { $snippets = []; - $table = code_snippets()->db->get_table_name(); - $base_dir = self::get_base_dir( $table, 'php' ); + $table = self::get_hashed_table_name( code_snippets()->db->get_table_name() ); + $snippets[ $table ] = self::load_active_snippets_from_file( + $table, + $snippet_type, + $scopes + ); + + if ( is_multisite() ) { + $ms_table = self::get_hashed_table_name( code_snippets()->db->get_table_name( true ) ); + + $root_base_dir = self::get_base_dir( $table ); + $active_shared_ids_file_path = $root_base_dir . '/active-shared-network-snippets.php'; + $active_shared_ids = is_file( $active_shared_ids_file_path ) + ? require $active_shared_ids_file_path + : []; + + $snippets[ $ms_table ] = self::load_active_snippets_from_file( + $ms_table, + $snippet_type, + $scopes, + $active_shared_ids + ); + } + + return $snippets; + } + + private static function load_active_snippets_from_file( + string $table, + string $snippet_type, + array $scopes, + ?array $active_shared_ids = null + ): array { + $snippets = []; + $db = code_snippets()->db; + + $base_dir = self::get_base_dir( $table, $snippet_type ); $snippets_file_path = $base_dir . '/index.php'; - if ( is_file( $snippets_file_path ) ) { - $site_snippets = is_file( $snippets_file_path ) ? require $snippets_file_path : []; + if ( ! is_file( $snippets_file_path ) ) { + return $snippets; + } - $snippets[ $table ] = array_filter( - $site_snippets, - function ( $snippet ) use ( $scopes ) { - return $snippet['active'] && in_array( $snippet['scope'], $scopes, true ); - } - ); + $cache_key = sprintf( + 'active_snippets_%s_%s', + sanitize_key( join( '_', $scopes ) ), + self::get_hashed_table_name( $db->table ) === $table ? $db->table : $db->ms_table + ); + + $cached_snippets = wp_cache_get( $cache_key, CACHE_GROUP ); + + if ( is_array( $cached_snippets ) ) { + return $cached_snippets; } - if ( is_multisite() ) { - $ms_table = code_snippets()->db->get_table_name( true ); - $ms_base_dir = self::get_base_dir( $ms_table, 'php' ); - $ms_snippets_file_path = $ms_base_dir . '/index.php'; + $file_snippets = require $snippets_file_path; - if ( is_file( $ms_snippets_file_path ) ) { - $ms_snippets = is_file( $ms_snippets_file_path ) ? require $ms_snippets_file_path : []; + $filtered_snippets = array_filter( + $file_snippets, + function ( $snippet ) use ( $scopes, $active_shared_ids ) { + $is_active = $snippet['active']; - $root_base_dir = self::get_base_dir( $table ); - $active_shared_ids_file_path = $root_base_dir . '/active-shared-network-snippets.php'; - $active_shared_ids = is_file( $active_shared_ids_file_path ) ? require $active_shared_ids_file_path : []; + if ( null !== $active_shared_ids ) { + $is_active = $is_active || in_array( + intval( $snippet['id'] ), + $active_shared_ids, + true + ); + } - $snippets[ $ms_table ] = array_filter( - $ms_snippets, - function ( $snippet ) use ( $active_shared_ids, $scopes ) { - return ( $snippet['active'] || in_array( intval( $snippet['id'] ), $active_shared_ids, true ) ) && in_array( $snippet['scope'], $scopes, true ); - } - ); + return $is_active && in_array( $snippet['scope'], $scopes, true ); } - } + ); - return $snippets; + wp_cache_set( $cache_key, $filtered_snippets, CACHE_GROUP ); + + return $filtered_snippets; } public function add_settings_fields( array $fields ): array { $fields['general']['enable_flat_files'] = [ - 'name' => __( 'Enable Flat Files', 'code-snippets' ), + 'name' => __( 'Enable file-based execution', 'code-snippets' ), 'type' => 'checkbox', - 'label' => __( 'Snippets will be executed from flat files instead of the database.', 'code-snippets' ), + 'label' => __( 'Snippets will be executed directly from files instead of the database.', 'code-snippets' ) . ' ' . sprintf( + '%s', + esc_url( 'https://codesnippets.pro/doc/file-based-execution/' ), + __( 'Learn more.', 'code-snippets' ) + ), ]; return $fields; @@ -235,24 +322,65 @@ public function create_all_flat_files( array $settings, array $input ): void { private function create_snippet_flat_files(): void { $db = code_snippets()->db; - $data = $db->fetch_active_snippets( Snippet::get_all_scopes() ); - if ( empty( $data ) ) { - return; - } + $scopes = Snippet::get_all_scopes(); + + $data = $db->fetch_active_snippets( $scopes ); foreach ( $data as $table_name => $active_snippets ) { foreach ( $active_snippets as $snippet ) { - $snippet_obj = get_snippet( $snippet['id'], $table_name === $db->ms_table ); + $snippet_obj = get_snippet( $snippet['id'], $db->ms_table === $table_name ); $this->handle_snippet( $snippet_obj, $table_name ); } } + + if ( is_multisite() ) { + $current_blog_id = get_current_blog_id(); + + $sites = get_sites( [ 'fields' => 'ids' ] ); + foreach ( $sites as $site_id ) { + switch_to_blog( $site_id ); + $db->set_table_vars(); + + $site_data = $db->fetch_active_snippets( $scopes ); + foreach ( $site_data as $table_name => $active_snippets ) { + foreach ( $active_snippets as $snippet ) { + $snippet_obj = get_snippet( $snippet['id'], false ); + $this->handle_snippet( $snippet_obj, $table_name ); + } + } + + restore_current_blog(); + } + + $db->set_table_vars(); + } } private function create_active_shared_network_snippets_config_file(): void { - $active_shared_network_snippets = get_option( 'active_shared_network_snippets' ); - if ( false !== $active_shared_network_snippets ) { - $this->create_active_shared_network_snippets_file( $active_shared_network_snippets ); + if ( is_multisite() ) { + $current_blog_id = get_current_blog_id(); + $sites = get_sites( [ 'fields' => 'ids' ] ); + $db = code_snippets()->db; + + foreach ( $sites as $site_id ) { + switch_to_blog( $site_id ); + $db->set_table_vars(); + + $active_shared_network_snippets = get_option( 'active_shared_network_snippets' ); + if ( false !== $active_shared_network_snippets ) { + $this->create_active_shared_network_snippets_file( $active_shared_network_snippets ); + } + + restore_current_blog(); + } + + $db->set_table_vars(); + } else { + $active_shared_network_snippets = get_option( 'active_shared_network_snippets' ); + if ( false !== $active_shared_network_snippets ) { + $this->create_active_shared_network_snippets_file( $active_shared_network_snippets ); + } } } } diff --git a/src/php/flat-files/handlers/php-snippet-handler.php b/src/php/flat-files/handlers/php-snippet-handler.php index 666617b0..5d306392 100644 --- a/src/php/flat-files/handlers/php-snippet-handler.php +++ b/src/php/flat-files/handlers/php-snippet-handler.php @@ -3,15 +3,15 @@ namespace Code_Snippets; class Php_Snippet_Handler implements Snippet_Type_Handler_Interface { - public function get_file_extension(): string { - return 'php'; - } + public function get_file_extension(): string { + return 'php'; + } - public function get_dir_name(): string { - return 'php'; - } + public function get_dir_name(): string { + return 'php'; + } - public function wrap_code( string $code ): string { - return "handler_registry = $handler_registry; - + public function __construct() { add_action( 'the_posts', [ $this, 'enqueue_highlighting' ] ); add_action( 'init', [ $this, 'setup_mce_plugin' ] ); @@ -245,9 +241,9 @@ protected function convert_boolean_attribute_flags( array $atts, array $boolean_ * @return string Full file path for the snippet. */ private function build_snippet_flat_file_path( string $table_name, Snippet $snippet ): string { - $handler = $this->handler_registry->get_handler( $snippet->get_type() ); + $handler = code_snippets()->snippet_handler_registry->get_handler( $snippet->get_type() ); - return WP_CONTENT_DIR . '/code-snippets/' . $table_name . '/' . $handler->get_dir_name() . '/' . $snippet->id . '.' . $handler->get_file_extension(); + return Snippet_Files::get_base_dir( $table_name, $handler->get_dir_name() ) . '/' . $snippet->id . '.' . $handler->get_file_extension(); } /** diff --git a/src/php/snippet-ops.php b/src/php/snippet-ops.php index 9bdb7741..f3789915 100644 --- a/src/php/snippet-ops.php +++ b/src/php/snippet-ops.php @@ -690,7 +690,7 @@ function execute_active_snippets_from_flat_files(): bool { // Detect if a snippet is currently being edited, and if so, spare it from execution. $edit_id = 0; - $edit_table = $db->table; + $edit_table = Snippet_Files::get_hashed_table_name( $db->table ); if ( wp_is_json_request() && ! empty( $_SERVER['REQUEST_URI'] ) ) { $url = wp_parse_url( esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ); @@ -702,7 +702,7 @@ function execute_active_snippets_from_flat_files(): bool { if ( ! empty( $url['query'] ) ) { wp_parse_str( $url['query'], $path_params ); $edit_table = isset( $path_params['network'] ) && rest_sanitize_boolean( $path_params['network'] ) ? - $db->ms_table : $db->table; + Snippet_Files::get_hashed_table_name( $db->ms_table ) : Snippet_Files::get_hashed_table_name( $db->table ); } } } @@ -718,24 +718,25 @@ function execute_active_snippets_from_flat_files(): bool { // If the snippet is a single-use snippet, deactivate it before execution to ensure that the process always happens. if ( 'single-use' === $snippet['scope'] ) { + $table_to_update = Snippet_Files::get_hashed_table_name( $db->table ) === $table_name ? $db->table : $db->ms_table; $active_shared_ids = get_option( 'active_shared_network_snippets', array() ); - if ( $table_name === $db->ms_table && is_array( $active_shared_ids ) && in_array( $snippet_id, $active_shared_ids, true ) ) { + if ( Snippet_Files::get_hashed_table_name( $db->ms_table ) === $table_name && is_array( $active_shared_ids ) && in_array( $snippet_id, $active_shared_ids, true ) ) { unset( $active_shared_ids[ array_search( $snippet_id, $active_shared_ids, true ) ] ); $active_shared_ids = array_values( $active_shared_ids ); update_option( 'active_shared_network_snippets', $active_shared_ids ); - clean_active_snippets_cache( $table_name ); + clean_active_snippets_cache( $table_to_update ); } else { $wpdb->update( - $table_name, + $table_to_update, array( 'active' => '0' ), array( 'id' => $snippet_id ), array( '%d' ), array( '%d' ) ); - clean_snippets_cache( $table_name ); + clean_snippets_cache( $table_to_update ); - $network = $table_name === $db->ms_table; + $network = Snippet_Files::get_hashed_table_name( $db->ms_table ) === $table_name; do_action( 'code_snippets/deactivate_snippet', $snippet_id, $network ); } } diff --git a/src/php/uninstall.php b/src/php/uninstall.php index 028aca5c..5afaefb6 100644 --- a/src/php/uninstall.php +++ b/src/php/uninstall.php @@ -79,8 +79,16 @@ function delete_flat_files_directory() { return; } - $fs = new \Code_Snippets\WordPress_Filesystem_Adapter(); - $fs->rmdir( $flat_files_dir, true ); + if ( ! function_exists( 'request_filesystem_credentials' ) ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + } + + global $wp_filesystem; + WP_Filesystem(); + + if ( $wp_filesystem && $wp_filesystem->is_dir( $flat_files_dir ) ) { + $wp_filesystem->delete( $flat_files_dir, true ); + } } /** From d263c52dbe05c89bf3974bf409e7303a48156ce6 Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 11:54:22 +0300 Subject: [PATCH 042/106] feat: Add version switch settings file to plugin initialization --- src/php/class-plugin.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index 92bebfa3..d1d63e5f 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -114,6 +114,7 @@ public function load_plugin() { // Settings component. require_once $includes_path . '/settings/settings-fields.php'; require_once $includes_path . '/settings/editor-preview.php'; + require_once $includes_path . '/settings/version-switch.php'; require_once $includes_path . '/settings/settings.php'; // Cloud List Table shared functions. From 2dce9399fbd8635246d7b9aa157bf1b49638ef9b Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 11:54:54 +0300 Subject: [PATCH 043/106] feat: add render callback --- src/php/settings/class-setting-field.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/php/settings/class-setting-field.php b/src/php/settings/class-setting-field.php index e8d64dcd..e9887456 100644 --- a/src/php/settings/class-setting-field.php +++ b/src/php/settings/class-setting-field.php @@ -117,7 +117,11 @@ public function render() { * Render a callback field. */ public function render_callback_field() { - call_user_func( $this->render_callback ); + if ( ! is_callable( $this->render_callback ) ) { + return; + } + + call_user_func( $this->render_callback, $this->args ); } /** From cb278531471f9834a4924b556954f3b7585fb658 Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 11:55:07 +0300 Subject: [PATCH 044/106] feat: add version switch settings --- src/php/settings/settings-fields.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 593635b3..864e661f 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -46,6 +46,9 @@ function get_default_settings(): array { 'keymap' => 'default', 'theme' => 'default', ], + 'version-switch' => [ + 'selected_version' => '', + ], ]; $defaults = apply_filters( 'code_snippets_settings_defaults', $defaults ); @@ -81,6 +84,19 @@ function get_settings_fields(): array { ], ]; + $fields['version-switch'] = [ + 'version_switcher' => [ + 'name' => __( 'Switch Version', 'code-snippets' ), + 'type' => 'callback', + 'render_callback' => 'Code_Snippets\Settings\VersionSwitch\render_version_switch_field', + ], + 'refresh_versions' => [ + 'name' => __( 'Refresh Versions', 'code-snippets' ), + 'type' => 'callback', + 'render_callback' => 'Code_Snippets\Settings\VersionSwitch\render_refresh_versions_field', + ], + ]; + $fields['general'] = [ 'activate_by_default' => [ 'name' => __( 'Activate by Default', 'code-snippets' ), From cc8faf977af7983b30e22903a6c7fb24cae09f2e Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 11:55:14 +0300 Subject: [PATCH 045/106] feat: add version switch section to settings --- src/php/settings/settings.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/php/settings/settings.php b/src/php/settings/settings.php index cf94f8b9..3d54b960 100644 --- a/src/php/settings/settings.php +++ b/src/php/settings/settings.php @@ -136,9 +136,10 @@ function update_setting( string $section, string $field, $new_value ): bool { */ function get_settings_sections(): array { $sections = array( - 'general' => __( 'General', 'code-snippets' ), - 'editor' => __( 'Code Editor', 'code-snippets' ), - 'debug' => __( 'Debug', 'code-snippets' ), + 'general' => __( 'General', 'code-snippets' ), + 'editor' => __( 'Code Editor', 'code-snippets' ), + 'debug' => __( 'Debug', 'code-snippets' ), + 'version-switch' => __( 'Version', 'code-snippets' ), ); return apply_filters( 'code_snippets_settings_sections', $sections ); From 690d75648689ee8f76d5844ccbffc025138cc4f1 Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 11:55:26 +0300 Subject: [PATCH 046/106] feat: implement version switching functionality for the plugin --- src/php/settings/version-switch.php | 386 ++++++++++++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 src/php/settings/version-switch.php diff --git a/src/php/settings/version-switch.php b/src/php/settings/version-switch.php new file mode 100644 index 00000000..0a4325ea --- /dev/null +++ b/src/php/settings/version-switch.php @@ -0,0 +1,386 @@ + $download_url ) { + if ( 'trunk' !== $version ) { + $versions[] = [ + 'version' => $version, + 'url' => $download_url, + ]; + } + } + + // Sort versions in descending order + usort( $versions, function( $a, $b ) { + return version_compare( $b['version'], $a['version'] ); + }); + + // Cache for 1 hour + set_transient( $transient_key, $versions, HOUR_IN_SECONDS ); + } + + return $versions; +} + +/** + * Get current plugin version + * + * @return string Current version + */ +function get_current_version(): string { + return defined( 'CODE_SNIPPETS_VERSION' ) ? CODE_SNIPPETS_VERSION : '0.0.0'; +} + +/** + * Check if a version switch is in progress + * + * @return bool True if switch is in progress + */ +function is_version_switch_in_progress(): bool { + return get_transient( 'code_snippets_version_switch_progress' ) !== false; +} + +/** + * Handle version switch request + * + * @param string $target_version Target version to switch to + * @return array Result array with success status and message + */ +function handle_version_switch( string $target_version ): array { + // Check user capabilities + if ( ! current_user_can( 'update_plugins' ) ) { + return [ + 'success' => false, + 'message' => __( 'You do not have permission to update plugins.', 'code-snippets' ), + ]; + } + + // Validate target version + $available_versions = get_available_versions(); + $version_exists = false; + $download_url = ''; + + foreach ( $available_versions as $version_info ) { + if ( $version_info['version'] === $target_version ) { + $version_exists = true; + $download_url = $version_info['url']; + break; + } + } + + if ( ! $version_exists ) { + return [ + 'success' => false, + 'message' => __( 'Invalid version specified.', 'code-snippets' ), + ]; + } + + // Check if already on target version + if ( get_current_version() === $target_version ) { + return [ + 'success' => false, + 'message' => __( 'Already on the specified version.', 'code-snippets' ), + ]; + } + + // Set switch in progress + set_transient( 'code_snippets_version_switch_progress', $target_version, 5 * MINUTE_IN_SECONDS ); + + // Include WordPress upgrade functions + if ( ! function_exists( 'wp_update_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/update.php'; + } + if ( ! function_exists( 'show_message' ) ) { + require_once ABSPATH . 'wp-admin/includes/misc.php'; + } + if ( ! class_exists( 'Plugin_Upgrader' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + } + + // Create upgrader instance + $upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() ); + + // Perform the upgrade/downgrade + $result = $upgrader->upgrade( plugin_basename( CODE_SNIPPETS_FILE ), [ + 'clear_destination' => true, + 'overwrite_package' => true, + ] ); + + // Clear progress transient + delete_transient( 'code_snippets_version_switch_progress' ); + + if ( is_wp_error( $result ) ) { + return [ + 'success' => false, + 'message' => $result->get_error_message(), + ]; + } + + if ( $result ) { + // Clear version cache + delete_transient( 'code_snippets_available_versions' ); + + return [ + 'success' => true, + 'message' => sprintf( + __( 'Successfully switched to version %s. Please refresh the page to see changes.', 'code-snippets' ), + $target_version + ), + ]; + } + + return [ + 'success' => false, + 'message' => __( 'Failed to switch versions. Please try again.', 'code-snippets' ), + ]; +} + +/** + * Render the version switch field + * + * @param array $args Field arguments + */ +function render_version_switch_field( array $args ): void { + $current_version = get_current_version(); + $available_versions = get_available_versions(); + $is_switching = is_version_switch_in_progress(); + + ?> +
+

+ + +

+ + +
+

+
+ +

+ + +

+ +

+ +

+ + + +

+ +

+ +
+ + + __( 'You do not have permission to update plugins.', 'code-snippets' ), + ] ); + } + + $target_version = sanitize_text_field( $_POST['target_version'] ?? '' ); + + if ( empty( $target_version ) ) { + wp_send_json_error( [ + 'message' => __( 'No target version specified.', 'code-snippets' ), + ] ); + } + + $result = handle_version_switch( $target_version ); + + if ( $result['success'] ) { + wp_send_json_success( $result ); + } else { + wp_send_json_error( $result ); + } +} + +// Register AJAX handler +add_action( 'wp_ajax_code_snippets_switch_version', __NAMESPACE__ . '\\ajax_switch_version' ); + +/** + * Render refresh versions cache button + * + * @param array $args Field arguments + */ +function render_refresh_versions_field( array $args ): void { + ?> + +

+ +

+ + + __( 'You do not have permission to manage options.', 'code-snippets' ), + ] ); + } + + // Clear the cache + delete_transient( 'code_snippets_available_versions' ); + + // Fetch fresh data + get_available_versions(); + + wp_send_json_success( [ + 'message' => __( 'Versions cache refreshed successfully.', 'code-snippets' ), + ] ); +} + +// Register AJAX handler +add_action( 'wp_ajax_code_snippets_refresh_versions', __NAMESPACE__ . '\\ajax_refresh_versions' ); From fcd6831edac4267d3f9eeebad2021cbf28f23c64 Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 11:55:32 +0300 Subject: [PATCH 047/106] feat: add version switch styles and update sections in settings --- src/css/settings.scss | 75 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/src/css/settings.scss b/src/css/settings.scss index 35817074..1a5ef20b 100644 --- a/src/css/settings.scss +++ b/src/css/settings.scss @@ -1,6 +1,6 @@ @use 'common/editor'; -$sections: general, editor, debug; +$sections: general, editor, debug, version-switch; p.submit { display: flex; @@ -128,3 +128,76 @@ body.js { .cloud-settings tbody tr:nth-child(n+5) { display: none; } + +// Version Switch Styles +.code-snippets-version-switch { + .current-version { + font-family: monospace; + font-size: 1.1em; + font-weight: bold; + color: #0073aa; + background: #f0f6fc; + padding: 2px 8px; + border-radius: 3px; + border: 1px solid #c3c4c7; + } + + #target_version { + min-width: 200px; + margin-inline-start: 8px; + } + + #switch-version-btn { + margin-inline-start: 8px; + + &[disabled] { + opacity: 0.6; + cursor: not-allowed; + } + } + + #version-switch-result { + margin-block-start: 12px; + + &.notice { + padding: 8px 12px; + border-radius: 4px; + } + } + + p.description { + font-style: italic; + color: #d63638; + background: #fcf0f1; + padding: 8px 12px; + border-radius: 4px; + border-left: 4px solid #d63638; + margin-block-start: 16px; + } + + .notice { + &.notice-success { + border-left-color: #00a32a; + } + + &.notice-error { + border-left-color: #d63638; + } + + &.notice-warning { + border-left-color: #dba617; + } + + &.notice-info { + border-left-color: #72aee6; + } + } +} + +.version-switch-settings { + .form-table { + th { + width: 180px; + } + } +} From 2e54814b6b37fe5fc3078a5ec7ba72545a80979a Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 20:19:14 +0300 Subject: [PATCH 048/106] feat: refactor version switch functions and improve error handling --- src/php/settings/version-switch.php | 280 ++++++++++++++++++++++------ 1 file changed, 224 insertions(+), 56 deletions(-) diff --git a/src/php/settings/version-switch.php b/src/php/settings/version-switch.php index 0a4325ea..be99d1ce 100644 --- a/src/php/settings/version-switch.php +++ b/src/php/settings/version-switch.php @@ -2,6 +2,12 @@ /** * Handles version switching functionality for the Code Snippets plugin. * + * This file provides a complete version switching system that allows users to: + * - View available plugin versions from WordPress.org + * - Switch between different versions safely + * - Track progress during version switching + * - Handle errors gracefully with detailed logging + * * @package Code_Snippets * @subpackage Settings */ @@ -10,17 +16,23 @@ use function Code_Snippets\code_snippets; +// Configuration constants for version switching +const VERSION_CACHE_KEY = 'code_snippets_available_versions'; +const PROGRESS_KEY = 'code_snippets_version_switch_progress'; +const VERSION_CACHE_DURATION = HOUR_IN_SECONDS; +const PROGRESS_TIMEOUT = 5 * MINUTE_IN_SECONDS; +const WORDPRESS_API_ENDPOINT = 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=code-snippets'; + /** * Get available plugin versions from WordPress.org repository * * @return array Array of version information */ function get_available_versions(): array { - $transient_key = 'code_snippets_available_versions'; - $versions = get_transient( $transient_key ); + $versions = get_transient( VERSION_CACHE_KEY ); if ( false === $versions ) { - $response = wp_remote_get( 'https://api.wordpress.org/plugins/info/1.2/?action=plugin_information&slug=code-snippets' ); + $response = wp_remote_get( WORDPRESS_API_ENDPOINT ); if ( is_wp_error( $response ) ) { return []; @@ -49,8 +61,8 @@ function get_available_versions(): array { return version_compare( $b['version'], $a['version'] ); }); - // Cache for 1 hour - set_transient( $transient_key, $versions, HOUR_IN_SECONDS ); + // Cache for configured duration + set_transient( VERSION_CACHE_KEY, $versions, VERSION_CACHE_DURATION ); } return $versions; @@ -71,55 +83,80 @@ function get_current_version(): string { * @return bool True if switch is in progress */ function is_version_switch_in_progress(): bool { - return get_transient( 'code_snippets_version_switch_progress' ) !== false; + return get_transient( PROGRESS_KEY ) !== false; } /** - * Handle version switch request + * Clear version-related caches * - * @param string $target_version Target version to switch to - * @return array Result array with success status and message + * @return void */ -function handle_version_switch( string $target_version ): array { - // Check user capabilities - if ( ! current_user_can( 'update_plugins' ) ) { +function clear_version_caches(): void { + delete_transient( VERSION_CACHE_KEY ); + delete_transient( PROGRESS_KEY ); +} + +/** + * Validate target version against available versions + * + * @param string $target_version Target version to validate + * @param array $available_versions Array of available versions + * @return array Validation result with success status, message, and download URL + */ +function validate_target_version( string $target_version, array $available_versions ): array { + if ( empty( $target_version ) ) { return [ 'success' => false, - 'message' => __( 'You do not have permission to update plugins.', 'code-snippets' ), + 'message' => __( 'No target version specified.', 'code-snippets' ), + 'download_url' => '', ]; } - // Validate target version - $available_versions = get_available_versions(); - $version_exists = false; - $download_url = ''; - foreach ( $available_versions as $version_info ) { if ( $version_info['version'] === $target_version ) { - $version_exists = true; - $download_url = $version_info['url']; - break; + return [ + 'success' => true, + 'message' => '', + 'download_url' => $version_info['url'], + ]; } } - if ( ! $version_exists ) { - return [ - 'success' => false, - 'message' => __( 'Invalid version specified.', 'code-snippets' ), - ]; - } + return [ + 'success' => false, + 'message' => __( 'Invalid version specified.', 'code-snippets' ), + 'download_url' => '', + ]; +} - // Check if already on target version - if ( get_current_version() === $target_version ) { - return [ - 'success' => false, - 'message' => __( 'Already on the specified version.', 'code-snippets' ), - ]; +/** + * Create a standardized error response + * + * @param string $message User-friendly error message + * @param string $technical_details Technical details for debugging (optional) + * @return array Error response array + */ +function create_error_response( string $message, string $technical_details = '' ): array { + if ( ! empty( $technical_details ) ) { + // Log technical details for debugging + if ( function_exists( 'error_log' ) ) { + error_log( sprintf( 'Code Snippets version switch error: %s. Details: %s', $message, $technical_details ) ); + } } - // Set switch in progress - set_transient( 'code_snippets_version_switch_progress', $target_version, 5 * MINUTE_IN_SECONDS ); + return [ + 'success' => false, + 'message' => $message, + ]; +} +/** + * Perform the actual version installation using WordPress upgrader + * + * @param string $download_url URL to download the plugin version + * @return bool|\WP_Error Installation result + */ +function perform_version_install( string $download_url ) { // Include WordPress upgrade functions if ( ! function_exists( 'wp_update_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/update.php'; @@ -131,28 +168,161 @@ function handle_version_switch( string $target_version ): array { require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; } - // Create upgrader instance - $upgrader = new \Plugin_Upgrader( new \Automatic_Upgrader_Skin() ); + // Create update handler (captures Ajax responses and errors) and upgrader instance + $update_handler = new \WP_Ajax_Upgrader_Skin(); + $upgrader = new \Plugin_Upgrader( $update_handler ); - // Perform the upgrade/downgrade - $result = $upgrader->upgrade( plugin_basename( CODE_SNIPPETS_FILE ), [ - 'clear_destination' => true, - 'overwrite_package' => true, + // Store the handler globally so we can access it later for error extraction + global $code_snippets_last_update_handler, $code_snippets_last_upgrader; + $code_snippets_last_update_handler = $update_handler; + $code_snippets_last_upgrader = $upgrader; + + // Perform the install/overwrite using the package download URL from WordPress.org + return $upgrader->install( $download_url, [ + 'overwrite_package' => true, + 'clear_update_cache' => true, ] ); +} + +/** + * Handle installation failure and extract useful error information + * + * @param string $target_version The target version that failed to install + * @param string $download_url The download URL used + * @param mixed $install_result The result from the upgrader + * @return array Error response with extracted information + */ +function handle_installation_failure( string $target_version, string $download_url, $install_result ): array { + global $code_snippets_last_update_handler, $code_snippets_last_upgrader; + + $handler_messages = extract_handler_messages( $code_snippets_last_update_handler, $code_snippets_last_upgrader ); + + // Log details for server-side debugging + log_version_switch_attempt( $target_version, $install_result, "URL: $download_url, Messages: $handler_messages" ); + + // Return a more informative message when possible (still user-friendly) + $fallback_message = __( 'Failed to switch versions. Please try again.', 'code-snippets' ); + if ( ! empty( $handler_messages ) ) { + // Trim and sanitize a bit for output + $short = wp_trim_words( wp_strip_all_tags( $handler_messages ), 40, '...' ); + $fallback_message = sprintf( '%s %s', $fallback_message, $short ); + } + + return [ + 'success' => false, + 'message' => $fallback_message, + ]; +} + +/** + * Extract helpful messages from the update handler + * + * @param mixed $update_handler The WP_Ajax_Upgrader_Skin instance + * @param mixed $upgrader The Plugin_Upgrader instance + * @return string Extracted messages + */ +function extract_handler_messages( $update_handler, $upgrader ): string { + $handler_messages = ''; + + if ( isset( $update_handler ) ) { + // Errors (WP_Ajax_Upgrader_Skin stores them) + if ( method_exists( $update_handler, 'get_errors' ) ) { + $errs = $update_handler->get_errors(); + if ( $errs instanceof \WP_Error && $errs->has_errors() ) { + $handler_messages .= implode( "\n", $errs->get_error_messages() ); + } + } + // Error messages string + if ( method_exists( $update_handler, 'get_error_messages' ) ) { + $em = $update_handler->get_error_messages(); + if ( $em ) { + $handler_messages .= "\n" . $em; + } + } + // Upgrade messages (feedback/info) + if ( method_exists( $update_handler, 'get_upgrade_messages' ) ) { + $upgrade_msgs = $update_handler->get_upgrade_messages(); + if ( is_array( $upgrade_msgs ) ) { + $handler_messages .= "\n" . implode( "\n", $upgrade_msgs ); + } elseif ( $upgrade_msgs ) { + $handler_messages .= "\n" . (string) $upgrade_msgs; + } + } + } + + // Fallback: if upgrader populated result with info, include it + if ( empty( $handler_messages ) && isset( $upgrader->result ) ) { + if ( is_wp_error( $upgrader->result ) ) { + $handler_messages = implode( "\n", $upgrader->result->get_error_messages() ); + } else { + $handler_messages = is_scalar( $upgrader->result ) ? (string) $upgrader->result : print_r( $upgrader->result, true ); + } + } + + return trim( $handler_messages ); +} + +/** + * Log version switch attempt for debugging + * + * @param string $target_version Target version + * @param mixed $result Installation result + * @param string $details Additional details + * @return void + */ +function log_version_switch_attempt( string $target_version, $result, string $details = '' ): void { + if ( function_exists( 'error_log' ) ) { + error_log( sprintf( + 'Code Snippets version switch failed. target=%s, result=%s, details=%s', + $target_version, + var_export( $result, true ), + $details + ) ); + } +} + +/** + * Handle version switch request + * + * @param string $target_version Target version to switch to + * @return array Result array with success status and message + */ +function handle_version_switch( string $target_version ): array { + // Check user capabilities + if ( ! current_user_can( 'update_plugins' ) ) { + return create_error_response( __( 'You do not have permission to update plugins.', 'code-snippets' ) ); + } + + // Validate target version + $available_versions = get_available_versions(); + $validation = validate_target_version( $target_version, $available_versions ); + + if ( ! $validation['success'] ) { + return create_error_response( $validation['message'] ); + } + + // Check if already on target version + if ( get_current_version() === $target_version ) { + return create_error_response( __( 'Already on the specified version.', 'code-snippets' ) ); + } + + // Set switch in progress + set_transient( PROGRESS_KEY, $target_version, PROGRESS_TIMEOUT ); + + // Perform the version installation + $install_result = perform_version_install( $validation['download_url'] ); // Clear progress transient - delete_transient( 'code_snippets_version_switch_progress' ); + delete_transient( PROGRESS_KEY ); - if ( is_wp_error( $result ) ) { - return [ - 'success' => false, - 'message' => $result->get_error_message(), - ]; + // Handle the result + if ( is_wp_error( $install_result ) ) { + return create_error_response( $install_result->get_error_message() ); } - if ( $result ) { - // Clear version cache - delete_transient( 'code_snippets_available_versions' ); + if ( $install_result ) { + // Clear version cache on success + delete_transient( VERSION_CACHE_KEY ); return [ 'success' => true, @@ -163,10 +333,8 @@ function handle_version_switch( string $target_version ): array { ]; } - return [ - 'success' => false, - 'message' => __( 'Failed to switch versions. Please try again.', 'code-snippets' ), - ]; + // If we get here, the installation failed but didn't return a WP_Error + return handle_installation_failure( $target_version, $validation['download_url'], $install_result ); } /** @@ -371,8 +539,8 @@ function ajax_refresh_versions(): void { ] ); } - // Clear the cache - delete_transient( 'code_snippets_available_versions' ); + // Clear the cache using our helper function + delete_transient( VERSION_CACHE_KEY ); // Fetch fresh data get_available_versions(); From 5af841c0caa47825b980768a0b7d314d1f50f8b0 Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 21:21:53 +0300 Subject: [PATCH 049/106] feat: move version warning to separate field --- src/php/settings/settings-fields.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 864e661f..6ec79b3e 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -95,6 +95,11 @@ function get_settings_fields(): array { 'type' => 'callback', 'render_callback' => 'Code_Snippets\Settings\VersionSwitch\render_refresh_versions_field', ], + 'version_warning' => [ + 'name' => '', + 'type' => 'callback', + 'render_callback' => 'Code_Snippets\Settings\VersionSwitch\render_version_switch_warning', + ], ]; $fields['general'] = [ From f6225abf5b609d31eaab940cb7fb8b47424adc34 Mon Sep 17 00:00:00 2001 From: Imants Date: Fri, 22 Aug 2025 21:22:05 +0300 Subject: [PATCH 050/106] feat: enhance version switch UI with warning and button state management --- src/php/settings/version-switch.php | 57 +++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/src/php/settings/version-switch.php b/src/php/settings/version-switch.php index be99d1ce..30fbaca7 100644 --- a/src/php/settings/version-switch.php +++ b/src/php/settings/version-switch.php @@ -378,32 +378,44 @@ function render_version_switch_field( array $args ): void {

-

- -

- -

-

-

- - - Date: Thu, 16 Oct 2025 16:01:09 +0300 Subject: [PATCH 068/106] feat: add debug option to enable version change section --- src/php/settings/settings-fields.php | 8 ++++++++ src/php/settings/settings.php | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/php/settings/settings-fields.php b/src/php/settings/settings-fields.php index 385791c4..a80f8801 100644 --- a/src/php/settings/settings-fields.php +++ b/src/php/settings/settings-fields.php @@ -50,6 +50,9 @@ function get_default_settings(): array { 'version-switch' => [ 'selected_version' => '', ], + 'debug' => [ + 'enable_version_change' => false, + ], ]; $defaults = apply_filters( 'code_snippets_settings_defaults', $defaults ); @@ -83,6 +86,11 @@ function get_settings_fields(): array { 'type' => 'action', 'desc' => __( 'Use this button to manually clear snippets caches.', 'code-snippets' ), ], + 'enable_version_change' => [ + 'name' => __( 'Version Change', 'code-snippets' ), + 'type' => 'checkbox', + 'label' => __( 'Enable the ability to switch or rollback versions of the Code Snippets core plugin.', 'code-snippets' ), + ], ]; $fields['version-switch'] = [ diff --git a/src/php/settings/settings.php b/src/php/settings/settings.php index 3d54b960..e359cca0 100644 --- a/src/php/settings/settings.php +++ b/src/php/settings/settings.php @@ -139,9 +139,14 @@ function get_settings_sections(): array { 'general' => __( 'General', 'code-snippets' ), 'editor' => __( 'Code Editor', 'code-snippets' ), 'debug' => __( 'Debug', 'code-snippets' ), - 'version-switch' => __( 'Version', 'code-snippets' ), ); + // Only show the Version section when the debug setting to enable version changes is enabled. + $enable_version = get_setting( 'debug', 'enable_version_change' ); + if ( $enable_version ) { + $sections['version-switch'] = __( 'Version', 'code-snippets' ); + } + return apply_filters( 'code_snippets_settings_sections', $sections ); } @@ -169,8 +174,13 @@ function register_plugin_settings() { add_settings_section( $section_id, $section_name, '__return_empty_string', 'code-snippets' ); } - // Register settings fields. + // Register settings fields. Only register fields for sections that exist (some sections may be gated by settings). + $registered_sections = get_settings_sections(); foreach ( get_settings_fields() as $section_id => $fields ) { + if ( ! isset( $registered_sections[ $section_id ] ) ) { + continue; + } + foreach ( $fields as $field_id => $field ) { $field_object = new Setting_Field( $section_id, $field_id, $field ); add_settings_field( $field_id, $field['name'], [ $field_object, 'render' ], 'code-snippets', $section_id ); From 616f251d27285cf987f010375f4744080799bc21 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 16 Oct 2025 16:16:29 +0300 Subject: [PATCH 069/106] fix: update version switch integration to use class-based implementation ref: https://github.com/codesnippetspro/code-snippets/pull/251#discussion_r2320156008 --- src/php/class-plugin.php | 2 +- src/php/settings/class-version-switch.php | 362 ++++++++++++++++++++++ src/php/settings/settings-fields.php | 8 +- 3 files changed, 367 insertions(+), 5 deletions(-) create mode 100644 src/php/settings/class-version-switch.php diff --git a/src/php/class-plugin.php b/src/php/class-plugin.php index 8fb5ac7b..79cd8a5b 100644 --- a/src/php/class-plugin.php +++ b/src/php/class-plugin.php @@ -125,7 +125,7 @@ public function load_plugin() { // Settings component. require_once $includes_path . '/settings/settings-fields.php'; require_once $includes_path . '/settings/editor-preview.php'; - require_once $includes_path . '/settings/version-switch.php'; + require_once $includes_path . '/settings/class-version-switch.php'; require_once $includes_path . '/settings/settings.php'; // Cloud List Table shared functions. diff --git a/src/php/settings/class-version-switch.php b/src/php/settings/class-version-switch.php new file mode 100644 index 00000000..7738147d --- /dev/null +++ b/src/php/settings/class-version-switch.php @@ -0,0 +1,362 @@ + $download_url ) { + if ( 'trunk' !== $version ) { + $versions[] = [ + 'version' => $version, + 'url' => $download_url, + ]; + } + } + + // Sort versions in descending order + usort( $versions, function( $a, $b ) { + return version_compare( $b['version'], $a['version'] ); + }); + + // Cache for configured duration + set_transient( VERSION_CACHE_KEY, $versions, VERSION_CACHE_DURATION ); + } + + return $versions; + } + + public static function get_current_version(): string { + return defined( 'CODE_SNIPPETS_VERSION' ) ? CODE_SNIPPETS_VERSION : '0.0.0'; + } + + public static function is_version_switch_in_progress(): bool { + return get_transient( PROGRESS_KEY ) !== false; + } + + public static function clear_version_caches(): void { + delete_transient( VERSION_CACHE_KEY ); + delete_transient( PROGRESS_KEY ); + } + + public static function validate_target_version( string $target_version, array $available_versions ): array { + if ( empty( $target_version ) ) { + return [ + 'success' => false, + 'message' => __( 'No target version specified.', 'code-snippets' ), + 'download_url' => '', + ]; + } + + foreach ( $available_versions as $version_info ) { + if ( $version_info['version'] === $target_version ) { + return [ + 'success' => true, + 'message' => '', + 'download_url' => $version_info['url'], + ]; + } + } + + return [ + 'success' => false, + 'message' => __( 'Invalid version specified.', 'code-snippets' ), + 'download_url' => '', + ]; + } + + public static function create_error_response( string $message, string $technical_details = '' ): array { + if ( ! empty( $technical_details ) ) { + if ( function_exists( 'error_log' ) ) { + error_log( sprintf( 'Code Snippets version switch error: %s. Details: %s', $message, $technical_details ) ); + } + } + + return [ + 'success' => false, + 'message' => $message, + ]; + } + + public static function perform_version_install( string $download_url ) { + if ( ! function_exists( 'wp_update_plugins' ) ) { + require_once ABSPATH . 'wp-admin/includes/update.php'; + } + if ( ! function_exists( 'show_message' ) ) { + require_once ABSPATH . 'wp-admin/includes/misc.php'; + } + if ( ! class_exists( 'Plugin_Upgrader' ) ) { + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + } + + $update_handler = new \WP_Ajax_Upgrader_Skin(); + $upgrader = new \Plugin_Upgrader( $update_handler ); + + global $code_snippets_last_update_handler, $code_snippets_last_upgrader; + $code_snippets_last_update_handler = $update_handler; + $code_snippets_last_upgrader = $upgrader; + + return $upgrader->install( $download_url, [ + 'overwrite_package' => true, + 'clear_update_cache' => true, + ] ); + } + + public static function extract_handler_messages( $update_handler, $upgrader ): string { + $handler_messages = ''; + + if ( isset( $update_handler ) ) { + if ( method_exists( $update_handler, 'get_errors' ) ) { + $errs = $update_handler->get_errors(); + if ( $errs instanceof \WP_Error && $errs->has_errors() ) { + $handler_messages .= implode( "\n", $errs->get_error_messages() ); + } + } + if ( method_exists( $update_handler, 'get_error_messages' ) ) { + $em = $update_handler->get_error_messages(); + if ( $em ) { + $handler_messages .= "\n" . $em; + } + } + if ( method_exists( $update_handler, 'get_upgrade_messages' ) ) { + $upgrade_msgs = $update_handler->get_upgrade_messages(); + if ( is_array( $upgrade_msgs ) ) { + $handler_messages .= "\n" . implode( "\n", $upgrade_msgs ); + } elseif ( $upgrade_msgs ) { + $handler_messages .= "\n" . (string) $upgrade_msgs; + } + } + } + + if ( empty( $handler_messages ) && isset( $upgrader->result ) ) { + if ( is_wp_error( $upgrader->result ) ) { + $handler_messages = implode( "\n", $upgrader->result->get_error_messages() ); + } else { + $handler_messages = is_scalar( $upgrader->result ) ? (string) $upgrader->result : print_r( $upgrader->result, true ); + } + } + + return trim( $handler_messages ); + } + + public static function log_version_switch_attempt( string $target_version, $result, string $details = '' ): void { + if ( function_exists( 'error_log' ) ) { + error_log( sprintf( 'Code Snippets version switch failed. target=%s, result=%s, details=%s', $target_version, var_export( $result, true ), $details ) ); + } + } + + public static function handle_installation_failure( string $target_version, string $download_url, $install_result ): array { + global $code_snippets_last_update_handler, $code_snippets_last_upgrader; + + $handler_messages = self::extract_handler_messages( $code_snippets_last_update_handler, $code_snippets_last_upgrader ); + self::log_version_switch_attempt( $target_version, $install_result, "URL: $download_url, Messages: $handler_messages" ); + + $fallback_message = __( 'Failed to switch versions. Please try again.', 'code-snippets' ); + if ( ! empty( $handler_messages ) ) { + $short = wp_trim_words( wp_strip_all_tags( $handler_messages ), 40, '...' ); + $fallback_message = sprintf( '%s %s', $fallback_message, $short ); + } + + return [ + 'success' => false, + 'message' => $fallback_message, + ]; + } + + public static function handle_version_switch( string $target_version ): array { + if ( ! current_user_can( 'update_plugins' ) ) { + return self::create_error_response( __( 'You do not have permission to update plugins.', 'code-snippets' ) ); + } + + $available_versions = self::get_available_versions(); + $validation = self::validate_target_version( $target_version, $available_versions ); + + if ( ! $validation['success'] ) { + return self::create_error_response( $validation['message'] ); + } + + if ( self::get_current_version() === $target_version ) { + return self::create_error_response( __( 'Already on the specified version.', 'code-snippets' ) ); + } + + set_transient( PROGRESS_KEY, $target_version, PROGRESS_TIMEOUT ); + + $install_result = self::perform_version_install( $validation['download_url'] ); + + delete_transient( PROGRESS_KEY ); + + if ( is_wp_error( $install_result ) ) { + return self::create_error_response( $install_result->get_error_message() ); + } + + if ( $install_result ) { + delete_transient( VERSION_CACHE_KEY ); + + return [ + 'success' => true, + 'message' => sprintf( __( 'Successfully switched to version %s. Please refresh the page to see changes.', 'code-snippets' ), $target_version ), + ]; + } + + return self::handle_installation_failure( $target_version, $validation['download_url'], $install_result ); + } + + public static function render_version_switch_field( array $args ): void { + $current_version = self::get_current_version(); + $available_versions = self::get_available_versions(); + $is_switching = self::is_version_switch_in_progress(); + + ?> +
+

+ + +

+ + +
+

+
+ +

+ + +

+ +

+ +

+ + + +
__( 'You do not have permission to update plugins.', 'code-snippets' ), + ] ); + } + + $target_version = sanitize_text_field( $_POST['target_version'] ?? '' ); + + if ( empty( $target_version ) ) { + wp_send_json_error( [ + 'message' => __( 'No target version specified.', 'code-snippets' ), + ] ); + } + + $result = self::handle_version_switch( $target_version ); + + if ( $result['success'] ) { + wp_send_json_success( $result ); + } else { + wp_send_json_error( $result ); + } + } + + public static function render_refresh_versions_field( array $args ): void { + ?> + +

+ +

__( 'You do not have permission to manage options.', 'code-snippets' ), + ] ); + } + + delete_transient( VERSION_CACHE_KEY ); + self::get_available_versions(); + + wp_send_json_success( [ + 'message' => __( 'Available versions updated successfully.', 'code-snippets' ), + ] ); + } + + public static function render_version_switch_warning(): void { + ?> + + 'action', 'desc' => __( 'Use this button to manually clear snippets caches.', 'code-snippets' ), ], - 'enable_version_change' => [ + 'enable_version_change' => [ 'name' => __( 'Version Change', 'code-snippets' ), 'type' => 'checkbox', 'label' => __( 'Enable the ability to switch or rollback versions of the Code Snippets core plugin.', 'code-snippets' ), @@ -97,17 +97,17 @@ function get_settings_fields(): array { 'version_switcher' => [ 'name' => __( 'Switch Version', 'code-snippets' ), 'type' => 'callback', - 'render_callback' => 'Code_Snippets\Settings\VersionSwitch\render_version_switch_field', + 'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_version_switch_field' ], ], 'refresh_versions' => [ 'name' => __( 'Refresh Versions', 'code-snippets' ), 'type' => 'callback', - 'render_callback' => 'Code_Snippets\Settings\VersionSwitch\render_refresh_versions_field', + 'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_refresh_versions_field' ], ], 'version_warning' => [ 'name' => '', 'type' => 'callback', - 'render_callback' => 'Code_Snippets\Settings\VersionSwitch\render_version_switch_warning', + 'render_callback' => [ '\\Code_Snippets\\Settings\\Version_Switch', 'render_version_switch_warning' ], ], ]; From 964da4303fd5fe0c38f6733935118cf188ec6458 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 16 Oct 2025 16:17:51 +0300 Subject: [PATCH 070/106] chore: remove unnecessary code --- src/php/settings/version-switch.php | 476 ---------------------------- 1 file changed, 476 deletions(-) delete mode 100644 src/php/settings/version-switch.php diff --git a/src/php/settings/version-switch.php b/src/php/settings/version-switch.php deleted file mode 100644 index d41381c0..00000000 --- a/src/php/settings/version-switch.php +++ /dev/null @@ -1,476 +0,0 @@ - $download_url ) { - if ( 'trunk' !== $version ) { - $versions[] = [ - 'version' => $version, - 'url' => $download_url, - ]; - } - } - - // Sort versions in descending order - usort( $versions, function( $a, $b ) { - return version_compare( $b['version'], $a['version'] ); - }); - - // Cache for configured duration - set_transient( VERSION_CACHE_KEY, $versions, VERSION_CACHE_DURATION ); - } - - return $versions; -} - -/** - * Get current plugin version - * - * @return string Current version - */ -function get_current_version(): string { - return defined( 'CODE_SNIPPETS_VERSION' ) ? CODE_SNIPPETS_VERSION : '0.0.0'; -} - -/** - * Check if a version switch is in progress - * - * @return bool True if switch is in progress - */ -function is_version_switch_in_progress(): bool { - return get_transient( PROGRESS_KEY ) !== false; -} - -/** - * Clear version-related caches - * - * @return void - */ -function clear_version_caches(): void { - delete_transient( VERSION_CACHE_KEY ); - delete_transient( PROGRESS_KEY ); -} - -/** - * Validate target version against available versions - * - * @param string $target_version Target version to validate - * @param array $available_versions Array of available versions - * @return array Validation result with success status, message, and download URL - */ -function validate_target_version( string $target_version, array $available_versions ): array { - if ( empty( $target_version ) ) { - return [ - 'success' => false, - 'message' => __( 'No target version specified.', 'code-snippets' ), - 'download_url' => '', - ]; - } - - foreach ( $available_versions as $version_info ) { - if ( $version_info['version'] === $target_version ) { - return [ - 'success' => true, - 'message' => '', - 'download_url' => $version_info['url'], - ]; - } - } - - return [ - 'success' => false, - 'message' => __( 'Invalid version specified.', 'code-snippets' ), - 'download_url' => '', - ]; -} - -/** - * Create a standardized error response - * - * @param string $message User-friendly error message - * @param string $technical_details Technical details for debugging (optional) - * @return array Error response array - */ -function create_error_response( string $message, string $technical_details = '' ): array { - if ( ! empty( $technical_details ) ) { - // Log technical details for debugging - if ( function_exists( 'error_log' ) ) { - error_log( sprintf( 'Code Snippets version switch error: %s. Details: %s', $message, $technical_details ) ); - } - } - - return [ - 'success' => false, - 'message' => $message, - ]; -} - -/** - * Perform the actual version installation using WordPress upgrader - * - * @param string $download_url URL to download the plugin version - * @return bool|\WP_Error Installation result - */ -function perform_version_install( string $download_url ) { - // Include WordPress upgrade functions - if ( ! function_exists( 'wp_update_plugins' ) ) { - require_once ABSPATH . 'wp-admin/includes/update.php'; - } - if ( ! function_exists( 'show_message' ) ) { - require_once ABSPATH . 'wp-admin/includes/misc.php'; - } - if ( ! class_exists( 'Plugin_Upgrader' ) ) { - require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; - } - - // Create update handler (captures Ajax responses and errors) and upgrader instance - $update_handler = new \WP_Ajax_Upgrader_Skin(); - $upgrader = new \Plugin_Upgrader( $update_handler ); - - // Store the handler globally so we can access it later for error extraction - global $code_snippets_last_update_handler, $code_snippets_last_upgrader; - $code_snippets_last_update_handler = $update_handler; - $code_snippets_last_upgrader = $upgrader; - - // Perform the install/overwrite using the package download URL from WordPress.org - return $upgrader->install( $download_url, [ - 'overwrite_package' => true, - 'clear_update_cache' => true, - ] ); -} - -/** - * Handle installation failure and extract useful error information - * - * @param string $target_version The target version that failed to install - * @param string $download_url The download URL used - * @param mixed $install_result The result from the upgrader - * @return array Error response with extracted information - */ -function handle_installation_failure( string $target_version, string $download_url, $install_result ): array { - global $code_snippets_last_update_handler, $code_snippets_last_upgrader; - - $handler_messages = extract_handler_messages( $code_snippets_last_update_handler, $code_snippets_last_upgrader ); - - // Log details for server-side debugging - log_version_switch_attempt( $target_version, $install_result, "URL: $download_url, Messages: $handler_messages" ); - - // Return a more informative message when possible (still user-friendly) - $fallback_message = __( 'Failed to switch versions. Please try again.', 'code-snippets' ); - if ( ! empty( $handler_messages ) ) { - // Trim and sanitize a bit for output - $short = wp_trim_words( wp_strip_all_tags( $handler_messages ), 40, '...' ); - $fallback_message = sprintf( '%s %s', $fallback_message, $short ); - } - - return [ - 'success' => false, - 'message' => $fallback_message, - ]; -} - -/** - * Extract helpful messages from the update handler - * - * @param mixed $update_handler The WP_Ajax_Upgrader_Skin instance - * @param mixed $upgrader The Plugin_Upgrader instance - * @return string Extracted messages - */ -function extract_handler_messages( $update_handler, $upgrader ): string { - $handler_messages = ''; - - if ( isset( $update_handler ) ) { - // Errors (WP_Ajax_Upgrader_Skin stores them) - if ( method_exists( $update_handler, 'get_errors' ) ) { - $errs = $update_handler->get_errors(); - if ( $errs instanceof \WP_Error && $errs->has_errors() ) { - $handler_messages .= implode( "\n", $errs->get_error_messages() ); - } - } - // Error messages string - if ( method_exists( $update_handler, 'get_error_messages' ) ) { - $em = $update_handler->get_error_messages(); - if ( $em ) { - $handler_messages .= "\n" . $em; - } - } - // Upgrade messages (feedback/info) - if ( method_exists( $update_handler, 'get_upgrade_messages' ) ) { - $upgrade_msgs = $update_handler->get_upgrade_messages(); - if ( is_array( $upgrade_msgs ) ) { - $handler_messages .= "\n" . implode( "\n", $upgrade_msgs ); - } elseif ( $upgrade_msgs ) { - $handler_messages .= "\n" . (string) $upgrade_msgs; - } - } - } - - // Fallback: if upgrader populated result with info, include it - if ( empty( $handler_messages ) && isset( $upgrader->result ) ) { - if ( is_wp_error( $upgrader->result ) ) { - $handler_messages = implode( "\n", $upgrader->result->get_error_messages() ); - } else { - $handler_messages = is_scalar( $upgrader->result ) ? (string) $upgrader->result : print_r( $upgrader->result, true ); - } - } - - return trim( $handler_messages ); -} - -/** - * Log version switch attempt for debugging - * - * @param string $target_version Target version - * @param mixed $result Installation result - * @param string $details Additional details - * @return void - */ -function log_version_switch_attempt( string $target_version, $result, string $details = '' ): void { - if ( function_exists( 'error_log' ) ) { - error_log( sprintf( - 'Code Snippets version switch failed. target=%s, result=%s, details=%s', - $target_version, - var_export( $result, true ), - $details - ) ); - } -} - -/** - * Handle version switch request - * - * @param string $target_version Target version to switch to - * @return array Result array with success status and message - */ -function handle_version_switch( string $target_version ): array { - - if ( ! current_user_can( 'update_plugins' ) ) { - return create_error_response( __( 'You do not have permission to update plugins.', 'code-snippets' ) ); - } - - $available_versions = get_available_versions(); - $validation = validate_target_version( $target_version, $available_versions ); - - if ( ! $validation['success'] ) { - return create_error_response( $validation['message'] ); - } - - if ( get_current_version() === $target_version ) { - return create_error_response( __( 'Already on the specified version.', 'code-snippets' ) ); - } - - set_transient( PROGRESS_KEY, $target_version, PROGRESS_TIMEOUT ); - - $install_result = perform_version_install( $validation['download_url'] ); - - delete_transient( PROGRESS_KEY ); - - if ( is_wp_error( $install_result ) ) { - return create_error_response( $install_result->get_error_message() ); - } - - if ( $install_result ) { - delete_transient( VERSION_CACHE_KEY ); - - return [ - 'success' => true, - 'message' => sprintf( - __( 'Successfully switched to version %s. Please refresh the page to see changes.', 'code-snippets' ), - $target_version - ), - ]; - } - - return handle_installation_failure( $target_version, $validation['download_url'], $install_result ); -} - -/** - * Render the version switch field - * - * @param array $args Field arguments - */ -function render_version_switch_field( array $args ): void { - $current_version = get_current_version(); - $available_versions = get_available_versions(); - $is_switching = is_version_switch_in_progress(); - - ?> -
-

- - -

- - -
-

-
- -

- - -

- -

- -

- - - -
__( 'You do not have permission to update plugins.', 'code-snippets' ), - ] ); - } - - $target_version = sanitize_text_field( $_POST['target_version'] ?? '' ); - - if ( empty( $target_version ) ) { - wp_send_json_error( [ - 'message' => __( 'No target version specified.', 'code-snippets' ), - ] ); - } - - $result = handle_version_switch( $target_version ); - - if ( $result['success'] ) { - wp_send_json_success( $result ); - } else { - wp_send_json_error( $result ); - } -} - -// Register AJAX handler -add_action( 'wp_ajax_code_snippets_switch_version', __NAMESPACE__ . '\\ajax_switch_version' ); - -/** - * Render refresh versions cache button - * - * @param array $args Field arguments - */ -function render_refresh_versions_field( array $args ): void { - ?> - -

- -

__( 'You do not have permission to manage options.', 'code-snippets' ), - ] ); - } - - // Clear the cache using our helper function - delete_transient( VERSION_CACHE_KEY ); - - // Fetch fresh data - get_available_versions(); - - wp_send_json_success( [ - 'message' => __( 'Available versions updated successfully.', 'code-snippets' ), - ] ); -} - -// Register AJAX handler -add_action( 'wp_ajax_code_snippets_refresh_versions', __NAMESPACE__ . '\\ajax_refresh_versions' ); - -/** - * Render the version switch warning that appears at the bottom - * This should be called after all other version-related fields - */ -function render_version_switch_warning(): void { - ?> - - Date: Thu, 16 Oct 2025 16:21:04 +0300 Subject: [PATCH 071/106] fix: notice styles --- src/css/settings.scss | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/css/settings.scss b/src/css/settings.scss index fee9f64f..2ff6dc76 100644 --- a/src/css/settings.scss +++ b/src/css/settings.scss @@ -183,23 +183,25 @@ body.js { } } - .notice { - &.notice-success { - border-left-color: #00a32a; - } - - &.notice-error { - border-left-color: #d63638; - } - - &.notice-warning { - border-left-color: #dba617; - } - - &.notice-info { - border-left-color: #72aee6; - } - } + .notice { + &.notice { + &-success { + border-left-color: #00a32a; + } + + &-error { + border-left-color: #d63638; + } + + &-warning { + border-left-color: #dba617; + } + + &-info { + border-left-color: #72aee6; + } + } + } } .version-switch-settings { From fe9754f1a327b41d84c48a53c9b8de7c96cfa806 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:08:54 +0000 Subject: [PATCH 072/106] chore(release): update changelog for v3.7.1-beta.1 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b058eab1..4da338bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog + +## [3.7.1-beta.1] - 2025-10-16 + +### Changed +* 54 files modified across 140 commits +* 19 PHP files updated +* 13 JavaScript files updated + ## 3.7.1 ([unreleased]) ### Fixed From 4c174da20cbf2a8a5df5c6dce4d75b89924d04e7 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:08:54 +0000 Subject: [PATCH 073/106] chore(release): update readme for v3.7.1-beta.1 --- src/readme.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/readme.txt b/src/readme.txt index ac8d5a2a..50b0444f 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -104,6 +104,12 @@ You can report security bugs found in the source code of this plugin through the == Changelog == + += 3.7.1-beta.1 2025-10-16 = + +__Changed__ +* 54 files modified across 140 commits + = 3.7.0 (beta release) = __Added__ From 984912866efa926ba8498f006ec23785ae9926ff Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:09:32 +0000 Subject: [PATCH 074/106] chore(release): bump version to v3.7.1-beta.1 --- package-lock.json | 4 ++-- package.json | 2 +- src/code-snippets.php | 6 +++--- src/readme.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 000857bc..c159d714 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-snippets", - "version": "3.7.0", + "version": "3.7.1-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-snippets", - "version": "3.7.0", + "version": "3.7.1-beta.1", "license": "GPL-2.0-or-later", "dependencies": { "@codemirror/fold": "^0.19.4", diff --git a/package.json b/package.json index 5269b101..94b9eadd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-snippets", "description": "Manage code snippets running on a WordPress-powered site through a graphical interface.", "homepage": "https://codesnippets.pro", - "version": "3.7.0", + "version": "3.7.1-beta.1", "main": "src/dist/edit.js", "directories": { "test": "tests" diff --git a/src/code-snippets.php b/src/code-snippets.php index d5a7ce77..0ed00973 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,11 +8,11 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.0 + * Version: 3.7.1-beta.1-beta.1 * Requires PHP: 7.4 * Requires at least: 5.0 * - * @version 3.7.0 + * @version 3.7.1-beta.1 * @package Code_Snippets * @author Shea Bunge * @copyright 2012-2024 Code Snippets Pro @@ -37,7 +37,7 @@ * * @const string */ - define( 'CODE_SNIPPETS_VERSION', '3.7.0' ); + define( 'CODE_SNIPPETS_VERSION', '3.7.1-beta.1' ); /** * The full path to the main file of this plugin. diff --git a/src/readme.txt b/src/readme.txt index 50b0444f..cb5aedf3 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -4,7 +4,7 @@ Donate link: https://codesnippets.pro Tags: code, snippets, multisite, php, css License: GPL-2.0-or-later License URI: license.txt -Stable tag: 3.7.0 +Stable tag: 3.7.1-beta.1 Tested up to: 6.8.2 An easy, clean and simple way to enhance your site with code snippets. From ccfedd1a316150a3ea193ebd182cb3d53b8d707b Mon Sep 17 00:00:00 2001 From: Code Snippets <139164393+code-snippets-bot@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:37:37 +0300 Subject: [PATCH 075/106] fix: changelog --- CHANGELOG.md | 8 -------- src/readme.txt | 6 ------ 2 files changed, 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4da338bc..b058eab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,5 @@ # Changelog - -## [3.7.1-beta.1] - 2025-10-16 - -### Changed -* 54 files modified across 140 commits -* 19 PHP files updated -* 13 JavaScript files updated - ## 3.7.1 ([unreleased]) ### Fixed diff --git a/src/readme.txt b/src/readme.txt index cb5aedf3..1f7e464b 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -104,12 +104,6 @@ You can report security bugs found in the source code of this plugin through the == Changelog == - -= 3.7.1-beta.1 2025-10-16 = - -__Changed__ -* 54 files modified across 140 commits - = 3.7.0 (beta release) = __Added__ From 7a5b4c8c31362483b02b7788d5d0d28b6d8fe689 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:57:12 +0000 Subject: [PATCH 076/106] chore(release): update changelog for v3.7.1-beta.1 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b058eab1..fbe808d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog + +## [3.7.1-beta.1] - 2025-10-16 + +### Added +* Optional flat-file storage mode for snippets with HTML/PHP handlers and a file-based repository, configurable in settings with uninstall cleanup. +* Added Carolina and Louis as plugin contributors (visible in plugin metadata/readme). + +### Changed +* Minor UI/UX tweaks to the editor form and sidebar; improved editor preview behavior. + +### Fixed +* Improved reliability of snippet evaluation and front-end integration. +* Stabilized release/version automation (prepare-tag, changelog job, version regex/outputs). + ## 3.7.1 ([unreleased]) ### Fixed From 50c8cbc63b9bc03ca36434b7b36eb884534da54f Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Thu, 16 Oct 2025 17:57:12 +0000 Subject: [PATCH 077/106] chore(release): update readme for v3.7.1-beta.1 --- src/readme.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/readme.txt b/src/readme.txt index 1f7e464b..5062f67c 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -104,6 +104,20 @@ You can report security bugs found in the source code of this plugin through the == Changelog == + += 3.7.1-beta.1 2025-10-16 = + +__Added__ +* Optional flat-file storage mode for snippets with HTML/PHP handlers and a file-based repository, configurable in settings with uninstall cleanup. +* Added Carolina and Louis as plugin contributors (visible in plugin metadata/readme). + +__Changed__ +* Minor UI/UX tweaks to the editor form and sidebar; improved editor preview behavior. + +__Fixed__ +* Improved reliability of snippet evaluation and front-end integration. +* Stabilized release/version automation (prepare-tag, changelog job, version regex/outputs). + = 3.7.0 (beta release) = __Added__ From 19b53ef79954591c7c42609f2b79496c8da819a1 Mon Sep 17 00:00:00 2001 From: Code Snippets <139164393+code-snippets-bot@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:13:24 +0300 Subject: [PATCH 078/106] fix: changelog --- CHANGELOG.md | 16 +++++++--------- src/readme.txt | 24 ++++++++++++++++++------ 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbe808d1..d1114e26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,20 @@ # Changelog -## [3.7.1-beta.1] - 2025-10-16 +## [3.7.1-beta.1] (2025-10-16) ### Added -* Optional flat-file storage mode for snippets with HTML/PHP handlers and a file-based repository, configurable in settings with uninstall cleanup. -* Added Carolina and Louis as plugin contributors (visible in plugin metadata/readme). +* Added @CarolinaOP and @louiswol94 as plugin contributors +* File-based execution mode for snippets (Optional) +* Playwright integration for automated testing +* Support for Composer package prefixing to prevent conflicts ### Changed -* Minor UI/UX tweaks to the editor form and sidebar; improved editor preview behavior. +* Minor UI/UX tweaks to the editor form and sidebar +* Improved editor preview behavior. ### Fixed * Improved reliability of snippet evaluation and front-end integration. -* Stabilized release/version automation (prepare-tag, changelog job, version regex/outputs). - -## 3.7.1 ([unreleased]) - -### Fixed * Prefixed Composer packages to reduce collisions with other plugins, especially those using Guzzle. * Functions conditions were loading before loop setup, resulting in some conditions not working. (PRO) * JavaScript and CSS snippets loading twice due to a conditions bug. (PRO) diff --git a/src/readme.txt b/src/readme.txt index 5062f67c..33cd55d4 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -104,21 +104,33 @@ You can report security bugs found in the source code of this plugin through the == Changelog == += 3.7.1-beta.1 (2025-10-16) = -= 3.7.1-beta.1 2025-10-16 = __Added__ -* Optional flat-file storage mode for snippets with HTML/PHP handlers and a file-based repository, configurable in settings with uninstall cleanup. -* Added Carolina and Louis as plugin contributors (visible in plugin metadata/readme). + +* Added @CarolinaOP and @louiswol94 as plugin contributors +* File-based execution mode for snippets (Optional) +* Playwright integration for automated testing +* Support for Composer package prefixing to prevent conflicts __Changed__ -* Minor UI/UX tweaks to the editor form and sidebar; improved editor preview behavior. + +* Minor UI/UX tweaks to the editor form and sidebar +* Improved editor preview behavior. __Fixed__ + * Improved reliability of snippet evaluation and front-end integration. -* Stabilized release/version automation (prepare-tag, changelog job, version regex/outputs). +* Prefixed Composer packages to reduce collisions with other plugins, especially those using Guzzle. +* Functions conditions were loading before loop setup, resulting in some conditions not working. (PRO) +* JavaScript and CSS snippets loading twice due to a conditions bug. (PRO) + +__Removed__ + +* Removed CSS linting within the editor until a modern replacement can be implemented. -= 3.7.0 (beta release) = += 3.7.0 (2025-08-29) = __Added__ From 66543b85df3acb7acdd0f50ec76801d24d263863 Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 16 Oct 2025 21:40:33 +0300 Subject: [PATCH 079/106] fix: changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1114e26..dd5570ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ ### Added * Added @CarolinaOP and @louiswol94 as plugin contributors -* File-based execution mode for snippets (Optional) +* File-based execution mode for snippets (Activate in Plugin Settings) * Playwright integration for automated testing * Support for Composer package prefixing to prevent conflicts From e78d6643fba5177a2f76e26210320e52debbebfa Mon Sep 17 00:00:00 2001 From: Imants Date: Thu, 16 Oct 2025 22:01:28 +0300 Subject: [PATCH 080/106] fix: changelog --- src/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/readme.txt b/src/readme.txt index 33cd55d4..05389b2a 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -110,7 +110,7 @@ You can report security bugs found in the source code of this plugin through the __Added__ * Added @CarolinaOP and @louiswol94 as plugin contributors -* File-based execution mode for snippets (Optional) +* File-based execution mode for snippets (Activate in Plugin Settings) * Playwright integration for automated testing * Support for Composer package prefixing to prevent conflicts From e39cb344ae2cad58960812d3b2d1463d5fe2c0e5 Mon Sep 17 00:00:00 2001 From: Imants Date: Tue, 21 Oct 2025 17:06:11 +0300 Subject: [PATCH 081/106] fix: correct version number in plugin header --- src/code-snippets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code-snippets.php b/src/code-snippets.php index 0ed00973..5c4ef911 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,7 +8,7 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.1-beta.1 + * Version: 3.7.1-beta.1 * Requires PHP: 7.4 * Requires at least: 5.0 * From a698ecc8266729ee7d4f8447bfcea47b3d3f5279 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Tue, 21 Oct 2025 14:33:24 +0000 Subject: [PATCH 082/106] chore(release): bump version to v3.7.1-beta.1 --- src/code-snippets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code-snippets.php b/src/code-snippets.php index 5c4ef911..0ed00973 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,7 +8,7 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.1 + * Version: 3.7.1-beta.1-beta.1 * Requires PHP: 7.4 * Requires at least: 5.0 * From e86b599ea57d536737c88755abdf817c92a3ff5a Mon Sep 17 00:00:00 2001 From: Imants Date: Tue, 21 Oct 2025 17:34:30 +0300 Subject: [PATCH 083/106] fix: version nr --- src/code-snippets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code-snippets.php b/src/code-snippets.php index 0ed00973..5c4ef911 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,7 +8,7 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.1-beta.1 + * Version: 3.7.1-beta.1 * Requires PHP: 7.4 * Requires at least: 5.0 * From 7bd15a02e717e7a01a682fb44dd7dd7c31c395da Mon Sep 17 00:00:00 2001 From: Imants Date: Tue, 21 Oct 2025 17:35:39 +0300 Subject: [PATCH 084/106] fix: double empty line --- src/readme.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/readme.txt b/src/readme.txt index 05389b2a..bb6fd98d 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -106,7 +106,6 @@ You can report security bugs found in the source code of this plugin through the = 3.7.1-beta.1 (2025-10-16) = - __Added__ * Added @CarolinaOP and @louiswol94 as plugin contributors From 80e3118f936edac5404254afd6b3443f5b731933 Mon Sep 17 00:00:00 2001 From: Imants Date: Tue, 21 Oct 2025 17:37:47 +0300 Subject: [PATCH 085/106] fix: changelog --- CHANGELOG.md | 4 +--- src/readme.txt | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd5570ab..33780035 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,7 @@ ### Added * Added @CarolinaOP and @louiswol94 as plugin contributors -* File-based execution mode for snippets (Activate in Plugin Settings) -* Playwright integration for automated testing -* Support for Composer package prefixing to prevent conflicts +* File-based execution mode for snippets (Optional in Plugin Settings) ### Changed * Minor UI/UX tweaks to the editor form and sidebar diff --git a/src/readme.txt b/src/readme.txt index bb6fd98d..09f08d38 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -109,9 +109,7 @@ You can report security bugs found in the source code of this plugin through the __Added__ * Added @CarolinaOP and @louiswol94 as plugin contributors -* File-based execution mode for snippets (Activate in Plugin Settings) -* Playwright integration for automated testing -* Support for Composer package prefixing to prevent conflicts +* File-based execution mode for snippets (Optional in Plugin Settings) __Changed__ From 8ca4a1b98f620660f528bb4891e627d6ee170fee Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 10:17:57 +0300 Subject: [PATCH 086/106] fix: revert stable tag to 3.7.0 --- src/readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/readme.txt b/src/readme.txt index 09f08d38..bdc8a70f 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -4,7 +4,7 @@ Donate link: https://codesnippets.pro Tags: code, snippets, multisite, php, css License: GPL-2.0-or-later License URI: license.txt -Stable tag: 3.7.1-beta.1 +Stable tag: 3.7.0 Tested up to: 6.8.2 An easy, clean and simple way to enhance your site with code snippets. From 173085403e6335500c71b1c980634172e4619a14 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 10:18:26 +0300 Subject: [PATCH 087/106] fix: conditionally update stable tag based on version type --- scripts/version.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/version.ts b/scripts/version.ts index a1716184..c5b70e01 100644 --- a/scripts/version.ts +++ b/scripts/version.ts @@ -15,8 +15,10 @@ replaceInFile( .replace(/(?'CODE_SNIPPETS_VERSION',\s+)'[\w-.]+'/, `$1'${plugin.version}'`) ) -replaceInFile( - 'src/readme.txt', - contents => contents - .replace(/(?Stable tag:\s+|@version\s+)\d+\.\d+[\w-.]+$/mg, `$1${plugin.version}`) -) +if (!/beta/i.test(plugin.version)) { + replaceInFile( + 'src/readme.txt', + contents => contents + .replace(/(?Stable tag:\s+|@version\s+)\d+\.\d+[\w-.]+$/mg, `$1${plugin.version}`) + ) +} From cad388876168497741297a0d83c7677fb76d80d7 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:30:18 +0000 Subject: [PATCH 088/106] chore(release): bump version to v3.7.1-beta.1 --- src/code-snippets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code-snippets.php b/src/code-snippets.php index 5c4ef911..0ed00973 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,7 +8,7 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.1 + * Version: 3.7.1-beta.1-beta.1 * Requires PHP: 7.4 * Requires at least: 5.0 * From 55335e032d6a3b9d093f93eacb47e51763db14c3 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 10:33:42 +0300 Subject: [PATCH 089/106] fix: version nr --- src/code-snippets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code-snippets.php b/src/code-snippets.php index 0ed00973..5c4ef911 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,7 +8,7 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.1-beta.1 + * Version: 3.7.1-beta.1 * Requires PHP: 7.4 * Requires at least: 5.0 * From 54fd5bdfdc6d875a14cfba404d991a7586389c4d Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:46:15 +0000 Subject: [PATCH 090/106] chore(release): update changelog for v3.7.1-beta.2 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33780035..2d8516e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Changelog + +## [3.7.1-beta.2] (2025-10-22) + +### Added +* Implemented version switching with a new 'Version Switch' section in Settings +* Added warning and confirmation UI with styled notices and button state management + +### Changed +* Improved error handling and user feedback during version switching +* Refined button and notice styling for clearer status visibility + +### Fixed +* Fixed admin notice styles to align with WordPress notice behavior +* Stabilized version switching by moving to a class-based integration and resolving related JS issues + ## [3.7.1-beta.1] (2025-10-16) ### Added From 5186122509642e81ac4433c775fa0a598dac81e5 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:46:15 +0000 Subject: [PATCH 091/106] chore(release): update readme for v3.7.1-beta.2 --- src/readme.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/readme.txt b/src/readme.txt index bdc8a70f..0d1f0178 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -104,6 +104,24 @@ You can report security bugs found in the source code of this plugin through the == Changelog == + += 3.7.1-beta.2 (2025-10-22) = + +__Added__ + +* Implemented version switching with a new 'Version Switch' section in Settings +* Added warning and confirmation UI with styled notices and button state management + +__Changed__ + +* Improved error handling and user feedback during version switching +* Refined button and notice styling for clearer status visibility + +__Fixed__ + +* Fixed admin notice styles to align with WordPress notice behavior +* Stabilized version switching by moving to a class-based integration and resolving related JS issues + = 3.7.1-beta.1 (2025-10-16) = __Added__ From 5a0c342fe020697fffaecb9b1cf7ea0ba0fa19e5 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 07:46:55 +0000 Subject: [PATCH 092/106] chore(release): bump version to v3.7.1-beta.2 --- package-lock.json | 4 ++-- package.json | 2 +- src/code-snippets.php | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c159d714..a6ff81b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-snippets", - "version": "3.7.1-beta.1", + "version": "3.7.1-beta.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-snippets", - "version": "3.7.1-beta.1", + "version": "3.7.1-beta.2", "license": "GPL-2.0-or-later", "dependencies": { "@codemirror/fold": "^0.19.4", diff --git a/package.json b/package.json index 94b9eadd..63237fe4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-snippets", "description": "Manage code snippets running on a WordPress-powered site through a graphical interface.", "homepage": "https://codesnippets.pro", - "version": "3.7.1-beta.1", + "version": "3.7.1-beta.2", "main": "src/dist/edit.js", "directories": { "test": "tests" diff --git a/src/code-snippets.php b/src/code-snippets.php index 5c4ef911..6ea2edc4 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,11 +8,11 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.1 + * Version: 3.7.1-beta.2-beta.2 * Requires PHP: 7.4 * Requires at least: 5.0 * - * @version 3.7.1-beta.1 + * @version 3.7.1-beta.2 * @package Code_Snippets * @author Shea Bunge * @copyright 2012-2024 Code Snippets Pro @@ -37,7 +37,7 @@ * * @const string */ - define( 'CODE_SNIPPETS_VERSION', '3.7.1-beta.1' ); + define( 'CODE_SNIPPETS_VERSION', '3.7.1-beta.2' ); /** * The full path to the main file of this plugin. From 9c468a34bf22ba93d6e9ab16d245a49362460a99 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 10:57:24 +0300 Subject: [PATCH 093/106] fix: changelog --- CHANGELOG.md | 9 --------- src/readme.txt | 11 ----------- 2 files changed, 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d8516e8..7b12e762 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,15 +6,6 @@ ### Added * Implemented version switching with a new 'Version Switch' section in Settings -* Added warning and confirmation UI with styled notices and button state management - -### Changed -* Improved error handling and user feedback during version switching -* Refined button and notice styling for clearer status visibility - -### Fixed -* Fixed admin notice styles to align with WordPress notice behavior -* Stabilized version switching by moving to a class-based integration and resolving related JS issues ## [3.7.1-beta.1] (2025-10-16) diff --git a/src/readme.txt b/src/readme.txt index 0d1f0178..b5b0bf43 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -110,17 +110,6 @@ You can report security bugs found in the source code of this plugin through the __Added__ * Implemented version switching with a new 'Version Switch' section in Settings -* Added warning and confirmation UI with styled notices and button state management - -__Changed__ - -* Improved error handling and user feedback during version switching -* Refined button and notice styling for clearer status visibility - -__Fixed__ - -* Fixed admin notice styles to align with WordPress notice behavior -* Stabilized version switching by moving to a class-based integration and resolving related JS issues = 3.7.1-beta.1 (2025-10-16) = From 449b0da3c26f565c45a4205d0f284e4d03b68862 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 10:58:19 +0300 Subject: [PATCH 094/106] fix: version --- src/code-snippets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code-snippets.php b/src/code-snippets.php index 6ea2edc4..abcf2270 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,7 +8,7 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.2-beta.2 + * Version: 3.7.1-beta.2 * Requires PHP: 7.4 * Requires at least: 5.0 * From 502965d6b73492ceea2ffb00b55b90d9330cd1ba Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 13:18:37 +0300 Subject: [PATCH 095/106] test: add REST API pagination test for snippets --- tests/phpunit/test-rest-api.php | 46 +++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/phpunit/test-rest-api.php diff --git a/tests/phpunit/test-rest-api.php b/tests/phpunit/test-rest-api.php new file mode 100644 index 00000000..80f11300 --- /dev/null +++ b/tests/phpunit/test-rest-api.php @@ -0,0 +1,46 @@ +name = "Snippet $i"; + $snippet->code = "// code $i"; + $result = \Code_Snippets\save_snippet( $snippet ); + $this->assertNotNull( $result ); + $created_ids[] = $result->id; + } + + // Request page 1 with per_page=10 + $request1 = new WP_REST_Request( 'GET', '/code-snippets/v1/snippets' ); + $request1->set_param( 'per_page', 10 ); + $request1->set_param( 'page', 1 ); + $response1 = rest_get_server()->dispatch( $request1 ); + $this->assertEquals( 200, $response1->get_status() ); + $data1 = $response1->get_data(); + $this->assertCount( 10, $data1 ); + + // Request page 2 with per_page=10 + $request2 = new WP_REST_Request( 'GET', '/code-snippets/v1/snippets' ); + $request2->set_param( 'per_page', 10 ); + $request2->set_param( 'page', 2 ); + $response2 = rest_get_server()->dispatch( $request2 ); + $this->assertEquals( 200, $response2->get_status() ); + $data2 = $response2->get_data(); + + // Ensure page 2 has the remaining items and is different from page 1 + $this->assertGreaterThanOrEqual( 1, count( $data2 ) ); + $this->assertNotEquals( $data1, $data2 ); + + // Check pagination headers + $this->assertEquals( '15', $response1->get_headers()['X-WP-Total'] ); + $this->assertEquals( '2', $response1->get_headers()['X-WP-TotalPages'] ); + } +} From fb9de70d656176be7717d4b5e8a69635850f8cc9 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 13:18:51 +0300 Subject: [PATCH 096/106] fix: pagination support to snippets REST API endpoint --- .../class-snippets-rest-controller.php | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index 18a1f827..49fbe59d 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -80,6 +80,9 @@ public function register_routes() { [ 'network' ] ); + // Allow standard collection parameters (page, per_page, etc.) on the collection route. + $collection_args = array_merge( $network_args, $this->get_collection_params() ); + register_rest_route( $this->namespace, $route, @@ -88,7 +91,7 @@ public function register_routes() { 'methods' => WP_REST_Server::READABLE, 'callback' => [ $this, 'get_items' ], 'permission_callback' => [ $this, 'get_items_permissions_check' ], - 'args' => $network_args, + 'args' => $collection_args, ], [ 'methods' => WP_REST_Server::CREATABLE, @@ -193,15 +196,44 @@ public function register_routes() { * @return WP_REST_Response Response object on success. */ public function get_items( $request ): WP_REST_Response { - $snippets = get_snippets(); - $snippets_data = []; + // Respect the optional 'network' param when fetching snippets. + $network = $request->get_param( 'network' ); + $all_snippets = get_snippets( [], $network ); + + // Collection params (page, per_page) are provided via route args. Use defaults + // from get_collection_params() when not present on the request. + $collection_params = $this->get_collection_params(); + $per_page = (int) $request->get_param( 'per_page' ); + if ( ! $per_page ) { + $per_page = isset( $collection_params['per_page']['default'] ) ? (int) $collection_params['per_page']['default'] : 10; + } + $per_page = max( 1, $per_page ); + + $page = (int) $request->get_param( 'page' ); + if ( ! $page ) { + $page = isset( $collection_params['page']['default'] ) ? (int) $collection_params['page']['default'] : 1; + } + $page = max( 1, $page ); + + $total_items = count( $all_snippets ); + $total_pages = (int) ceil( $total_items / $per_page ); + + // Slice the full list to the requested page. + $offset = ( $page - 1 ) * $per_page; + $snippets = array_slice( $all_snippets, $offset, $per_page ); + $snippets_data = []; foreach ( $snippets as $snippet ) { $snippet_data = $this->prepare_item_for_response( $snippet, $request ); $snippets_data[] = $this->prepare_response_for_collection( $snippet_data ); } - return rest_ensure_response( $snippets_data ); + $response = rest_ensure_response( $snippets_data ); + // Provide total counts in the response headers like WP core REST responses do. + $response->header( 'X-WP-Total', (string) $total_items ); + $response->header( 'X-WP-TotalPages', (string) $total_pages ); + + return $response; } /** From 811be0bea73d4274fe8ac717bb87a3015ebd6b24 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 13:24:52 +0300 Subject: [PATCH 097/106] fix: stylelint --- src/css/settings.scss | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/css/settings.scss b/src/css/settings.scss index 2ff6dc76..e2a040ae 100644 --- a/src/css/settings.scss +++ b/src/css/settings.scss @@ -142,7 +142,7 @@ body.js { } #target_version { - min-width: 200px; + min-inline-size: 200px; margin-inline-start: 8px; } @@ -158,9 +158,9 @@ body.js { // Warning box styling #version-switch-warning { - margin-top: 20px !important; + margin-block-start: 20px !important; padding: 12px 16px; - border-left: 4px solid #dba617; + border-inline-start: 4px solid #dba617; background: #fff8e5; border-radius: 4px; @@ -183,31 +183,31 @@ body.js { } } - .notice { - &.notice { - &-success { - border-left-color: #00a32a; - } + .notice { + &.notice { + &-success { + border-inline-start-color: #00a32a; + } - &-error { - border-left-color: #d63638; - } + &-error { + border-inline-start-color: #d63638; + } - &-warning { - border-left-color: #dba617; - } + &-warning { + border-inline-start-color: #dba617; + } - &-info { - border-left-color: #72aee6; - } - } - } + &-info { + border-inline-start-color: #72aee6; + } + } + } } .version-switch-settings { .form-table { th { - width: 180px; + inline-size: 180px; } } } From df8af7b3ad9fd678c0a57a342e86ac3aab2d99cf Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 15:10:14 +0300 Subject: [PATCH 098/106] fix: get_items method to support pagination --- src/php/rest-api/class-snippets-rest-controller.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index 49fbe59d..25b6ccdc 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -189,19 +189,17 @@ public function register_routes() { } /** - * Retrieves a collection of snippets. + * Retrieves a collection of snippets, with pagination. * * @param WP_REST_Request $request Full details about the request. * * @return WP_REST_Response Response object on success. */ public function get_items( $request ): WP_REST_Response { - // Respect the optional 'network' param when fetching snippets. $network = $request->get_param( 'network' ); $all_snippets = get_snippets( [], $network ); - // Collection params (page, per_page) are provided via route args. Use defaults - // from get_collection_params() when not present on the request. + // Collection params (page, per_page) $collection_params = $this->get_collection_params(); $per_page = (int) $request->get_param( 'per_page' ); if ( ! $per_page ) { @@ -229,7 +227,6 @@ public function get_items( $request ): WP_REST_Response { } $response = rest_ensure_response( $snippets_data ); - // Provide total counts in the response headers like WP core REST responses do. $response->header( 'X-WP-Total', (string) $total_items ); $response->header( 'X-WP-TotalPages', (string) $total_pages ); From 93c700a1df02a077103035a007ad881c675688a6 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 15:20:30 +0300 Subject: [PATCH 099/106] fix: remove unused file --- tests/phpunit/test-rest-api.php | 46 --------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 tests/phpunit/test-rest-api.php diff --git a/tests/phpunit/test-rest-api.php b/tests/phpunit/test-rest-api.php deleted file mode 100644 index 80f11300..00000000 --- a/tests/phpunit/test-rest-api.php +++ /dev/null @@ -1,46 +0,0 @@ -name = "Snippet $i"; - $snippet->code = "// code $i"; - $result = \Code_Snippets\save_snippet( $snippet ); - $this->assertNotNull( $result ); - $created_ids[] = $result->id; - } - - // Request page 1 with per_page=10 - $request1 = new WP_REST_Request( 'GET', '/code-snippets/v1/snippets' ); - $request1->set_param( 'per_page', 10 ); - $request1->set_param( 'page', 1 ); - $response1 = rest_get_server()->dispatch( $request1 ); - $this->assertEquals( 200, $response1->get_status() ); - $data1 = $response1->get_data(); - $this->assertCount( 10, $data1 ); - - // Request page 2 with per_page=10 - $request2 = new WP_REST_Request( 'GET', '/code-snippets/v1/snippets' ); - $request2->set_param( 'per_page', 10 ); - $request2->set_param( 'page', 2 ); - $response2 = rest_get_server()->dispatch( $request2 ); - $this->assertEquals( 200, $response2->get_status() ); - $data2 = $response2->get_data(); - - // Ensure page 2 has the remaining items and is different from page 1 - $this->assertGreaterThanOrEqual( 1, count( $data2 ) ); - $this->assertNotEquals( $data1, $data2 ); - - // Check pagination headers - $this->assertEquals( '15', $response1->get_headers()['X-WP-Total'] ); - $this->assertEquals( '2', $response1->get_headers()['X-WP-TotalPages'] ); - } -} From c0aa1a72ee843d9de02027aa2089077bba77066c Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 15:24:12 +0300 Subject: [PATCH 100/106] fix(copilot): comment style Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/php/rest-api/class-snippets-rest-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index 25b6ccdc..d6a904f9 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -199,7 +199,7 @@ public function get_items( $request ): WP_REST_Response { $network = $request->get_param( 'network' ); $all_snippets = get_snippets( [], $network ); - // Collection params (page, per_page) + // Collection params (page, per_page). $collection_params = $this->get_collection_params(); $per_page = (int) $request->get_param( 'per_page' ); if ( ! $per_page ) { From ff728863e39e54ad1838bc7679dec860fc7dd657 Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 15:39:22 +0300 Subject: [PATCH 101/106] fix: improve pagination --- .../class-snippets-rest-controller.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/php/rest-api/class-snippets-rest-controller.php b/src/php/rest-api/class-snippets-rest-controller.php index d6a904f9..6fe58ba9 100644 --- a/src/php/rest-api/class-snippets-rest-controller.php +++ b/src/php/rest-api/class-snippets-rest-controller.php @@ -199,20 +199,15 @@ public function get_items( $request ): WP_REST_Response { $network = $request->get_param( 'network' ); $all_snippets = get_snippets( [], $network ); - // Collection params (page, per_page). + // Get collection params (page, per_page). $collection_params = $this->get_collection_params(); - $per_page = (int) $request->get_param( 'per_page' ); - if ( ! $per_page ) { - $per_page = isset( $collection_params['per_page']['default'] ) ? (int) $collection_params['per_page']['default'] : 10; - } - $per_page = max( 1, $per_page ); + $per_page_request = (int) $request->get_param( 'per_page' ); + $per_page = max( 1, $per_page_request ? $per_page_request : (int) $collection_params['per_page']['default'] ); - $page = (int) $request->get_param( 'page' ); - if ( ! $page ) { - $page = isset( $collection_params['page']['default'] ) ? (int) $collection_params['page']['default'] : 1; - } - $page = max( 1, $page ); + $page_request = (int) $request->get_param( 'page' ); + $page = max( 1, $page_request ? $page_request : (int) $collection_params['page']['default'] ); + // Count total items $total_items = count( $all_snippets ); $total_pages = (int) ceil( $total_items / $per_page ); @@ -221,6 +216,7 @@ public function get_items( $request ): WP_REST_Response { $snippets = array_slice( $all_snippets, $offset, $per_page ); $snippets_data = []; + foreach ( $snippets as $snippet ) { $snippet_data = $this->prepare_item_for_response( $snippet, $request ); $snippets_data[] = $this->prepare_response_for_collection( $snippet_data ); From e38443222c26b850c47d8abd11bd037c176f7fb9 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:23:35 +0000 Subject: [PATCH 102/106] chore(release): update changelog for v3.7.1-beta.3 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b12e762..2885abe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,20 @@ + +## [3.7.1-beta.3] (2025-10-22) + +### Added +* Snippets REST API now supports pagination via page and per_page query parameters. +* Collection responses include pagination metadata headers for total items and total pages. + +### Changed +* Settings UI CSS switched to logical properties for better RTL support and consistent layout. + +### Fixed +* Snippets REST API now correctly respects the network parameter when listing snippets. +* Pagination logic corrected to reliably slice results and return stable pages. + ## [3.7.1-beta.2] (2025-10-22) ### Added From c7d9c1079faf17d9cb6c1a2cfba0db9b8d57da24 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:23:35 +0000 Subject: [PATCH 103/106] chore(release): update readme for v3.7.1-beta.3 --- src/readme.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/readme.txt b/src/readme.txt index b5b0bf43..55844435 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -105,6 +105,23 @@ You can report security bugs found in the source code of this plugin through the == Changelog == + += 3.7.1-beta.3 (2025-10-22) = + +__Added__ + +* Snippets REST API now supports pagination via page and per_page query parameters. +* Collection responses include pagination metadata headers for total items and total pages. + +__Changed__ + +* Settings UI CSS switched to logical properties for better RTL support and consistent layout. + +__Fixed__ + +* Snippets REST API now correctly respects the network parameter when listing snippets. +* Pagination logic corrected to reliably slice results and return stable pages. + = 3.7.1-beta.2 (2025-10-22) = __Added__ From e1e12699647b87e817fd555bc72bdc5bc5617007 Mon Sep 17 00:00:00 2001 From: code-snippets-bot <139164393+code-snippets-bot@users.noreply.github.com> Date: Wed, 22 Oct 2025 13:24:15 +0000 Subject: [PATCH 104/106] chore(release): bump version to v3.7.1-beta.3 --- package-lock.json | 4 ++-- package.json | 2 +- src/code-snippets.php | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index a6ff81b7..fe96a6f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "code-snippets", - "version": "3.7.1-beta.2", + "version": "3.7.1-beta.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "code-snippets", - "version": "3.7.1-beta.2", + "version": "3.7.1-beta.3", "license": "GPL-2.0-or-later", "dependencies": { "@codemirror/fold": "^0.19.4", diff --git a/package.json b/package.json index 63237fe4..8d664089 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "code-snippets", "description": "Manage code snippets running on a WordPress-powered site through a graphical interface.", "homepage": "https://codesnippets.pro", - "version": "3.7.1-beta.2", + "version": "3.7.1-beta.3", "main": "src/dist/edit.js", "directories": { "test": "tests" diff --git a/src/code-snippets.php b/src/code-snippets.php index abcf2270..9f7789c1 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,11 +8,11 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.2 + * Version: 3.7.1-beta.3-beta.3 * Requires PHP: 7.4 * Requires at least: 5.0 * - * @version 3.7.1-beta.2 + * @version 3.7.1-beta.3 * @package Code_Snippets * @author Shea Bunge * @copyright 2012-2024 Code Snippets Pro @@ -37,7 +37,7 @@ * * @const string */ - define( 'CODE_SNIPPETS_VERSION', '3.7.1-beta.2' ); + define( 'CODE_SNIPPETS_VERSION', '3.7.1-beta.3' ); /** * The full path to the main file of this plugin. From 27a80f2edb01c34f79eca51cede8fb75e7c9c08e Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 16:27:47 +0300 Subject: [PATCH 105/106] fix: changelog --- CHANGELOG.md | 7 ------- src/readme.txt | 9 --------- 2 files changed, 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2885abe9..5a333535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,14 +7,7 @@ ### Added * Snippets REST API now supports pagination via page and per_page query parameters. -* Collection responses include pagination metadata headers for total items and total pages. -### Changed -* Settings UI CSS switched to logical properties for better RTL support and consistent layout. - -### Fixed -* Snippets REST API now correctly respects the network parameter when listing snippets. -* Pagination logic corrected to reliably slice results and return stable pages. ## [3.7.1-beta.2] (2025-10-22) diff --git a/src/readme.txt b/src/readme.txt index 55844435..167f9bf3 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -111,16 +111,7 @@ You can report security bugs found in the source code of this plugin through the __Added__ * Snippets REST API now supports pagination via page and per_page query parameters. -* Collection responses include pagination metadata headers for total items and total pages. -__Changed__ - -* Settings UI CSS switched to logical properties for better RTL support and consistent layout. - -__Fixed__ - -* Snippets REST API now correctly respects the network parameter when listing snippets. -* Pagination logic corrected to reliably slice results and return stable pages. = 3.7.1-beta.2 (2025-10-22) = From 95af256ad459ab4789058bd6712d26e23fd85e4c Mon Sep 17 00:00:00 2001 From: Imants Date: Wed, 22 Oct 2025 16:27:56 +0300 Subject: [PATCH 106/106] fix: version --- src/code-snippets.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/code-snippets.php b/src/code-snippets.php index 9f7789c1..1db3c0bc 100644 --- a/src/code-snippets.php +++ b/src/code-snippets.php @@ -8,7 +8,7 @@ * License: GPL-2.0-or-later * License URI: license.txt * Text Domain: code-snippets - * Version: 3.7.1-beta.3-beta.3 + * Version: 3.7.1-beta.3 * Requires PHP: 7.4 * Requires at least: 5.0 *