From 0cb3bc7eee270ba6d79c4c154d9cd6aac5a20d64 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Mon, 7 Jul 2025 13:09:06 +0200 Subject: [PATCH 1/5] Simplify nested conditions --- admin-page.php | 90 ++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/admin-page.php b/admin-page.php index c89f4ddc..25237603 100644 --- a/admin-page.php +++ b/admin-page.php @@ -46,68 +46,66 @@ function sqlite_integration_admin_screen() { ); ?>

- - -
-

-
- - -
-

- ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php' - ); - ?> -

-
- + +
+

+
+ + +
+

' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php' ); ?> - - -

-

- ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php' - ); - ?> -

-
- - +

+
+ + ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php' + ); + ?> + +

' . esc_html( basename( WP_CONTENT_DIR ) ) . '' + esc_html__( 'The SQLite plugin cannot be activated because a different %s drop-in already exists.', 'sqlite-database-integration' ), + '' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php' ); ?>

- -
-

-
-

-

-

- - + +
+

+ ' . esc_html( basename( WP_CONTENT_DIR ) ) . '' + ); + ?> +

+
+ +
+

+
+

+

+

+ + Date: Mon, 7 Jul 2025 16:12:56 +0200 Subject: [PATCH 2/5] Enable overriding Query Monitor's "wp-content/db.php" file --- activate.php | 22 ++++++++++++++++++++++ admin-page.php | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/activate.php b/activate.php index 197f162c..1001914a 100644 --- a/activate.php +++ b/activate.php @@ -82,6 +82,28 @@ function sqlite_plugin_copy_db_file() { $destination = WP_CONTENT_DIR . '/db.php'; + /* + * When an existing "db.php" drop-in is detected, let's check if it's a known + * plugin that we can continue supporting even when we override the drop-in. + */ + $override_db_dropin = false; + if ( file_exists( $destination ) ) { + // Check for the Query Monitor plugin. + // When "QM_DB" exists, it must have been loaded via the "db.php" file. + if ( class_exists( 'QM_DB', false ) ) { + $override_db_dropin = true; + } + + if ( $override_db_dropin ) { + require_once ABSPATH . '/wp-admin/includes/file.php'; + global $wp_filesystem; + if ( ! $wp_filesystem ) { + WP_Filesystem(); + } + $wp_filesystem->delete( $destination ); + } + } + // Place database drop-in if not present yet, except in case there is // another database drop-in present already. if ( ! defined( 'SQLITE_DB_DROPIN_VERSION' ) && ! file_exists( $destination ) ) { diff --git a/admin-page.php b/admin-page.php index 25237603..cd8613cc 100644 --- a/admin-page.php +++ b/admin-page.php @@ -26,6 +26,21 @@ function sqlite_add_admin_menu() { * The admin page contents. */ function sqlite_integration_admin_screen() { + $db_dropin_path = WP_CONTENT_DIR . '/db.php'; + + /* + * When an existing "db.php" drop-in is detected, let's check if it's a known + * plugin that we can continue supporting even when we override the drop-in. + */ + $override_db_dropin = false; + if ( file_exists( $db_dropin_path ) && ! defined( 'SQLITE_DB_DROPIN_VERSION' ) ) { + // Check for the Query Monitor plugin. + // When "QM_DB" exists, it must have been loaded via the "db.php" file. + if ( class_exists( 'QM_DB', false ) ) { + $override_db_dropin = true; + } + } + ?>

@@ -50,7 +65,7 @@ function sqlite_integration_admin_screen() {

- +

@@ -103,6 +118,22 @@ function sqlite_integration_admin_screen() {

+ + +

+ NOTE: + ' . esc_html( basename( WP_CONTENT_DIR ) ) . '/db.php' + ); + ?> + + +

+ +

From 8bae35efe509beed118d405427d6adecc340fb08 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Mon, 7 Jul 2025 16:38:33 +0200 Subject: [PATCH 3/5] Implement basic integration with Query Monitor --- integrations/query-monitor/boot.php | 113 ++++++++++++++++++++++ wp-includes/sqlite/class-wp-sqlite-db.php | 31 ++++++ wp-includes/sqlite/db.php | 3 + 3 files changed, 147 insertions(+) create mode 100644 integrations/query-monitor/boot.php diff --git a/integrations/query-monitor/boot.php b/integrations/query-monitor/boot.php new file mode 100644 index 00000000..35ec433f --- /dev/null +++ b/integrations/query-monitor/boot.php @@ -0,0 +1,113 @@ +options ) { + global $table_prefix; + $wpdb->set_prefix( $table_prefix ?? '' ); +} + +$query_monitor_active = false; +try { + $value = $wpdb->get_row( + $wpdb->prepare( + "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", + 'active_plugins' + ) + ); + $query_monitor_active = in_array( + 'query-monitor/query-monitor.php', + unserialize( $value->option_value ), + true + ); +} catch ( Throwable $e ) { + return; +} + +if ( ! $query_monitor_active ) { + return; +} + +// 3. Determine the plugins directory. +if ( defined( 'WP_PLUGIN_DIR' ) ) { + $plugins_dir = WP_PLUGIN_DIR; +} else { + $plugins_dir = WP_CONTENT_DIR . '/plugins'; +} + +// 4. Load Query Monitor (as per the original "db.php" file). +$qm_dir = "{$plugins_dir}/query-monitor"; +$qm_php = "{$qm_dir}/classes/PHP.php"; + +if ( ! is_readable( $qm_php ) ) { + return; +} +require_once $qm_php; + +if ( ! QM_PHP::version_met() ) { + return; +} + +if ( ! file_exists( "{$qm_dir}/vendor/autoload.php" ) ) { + add_action( 'all_admin_notices', 'QM_PHP::vendor_nope' ); + return; +} + +require_once "{$qm_dir}/vendor/autoload.php"; + +if ( ! class_exists( 'QM_Backtrace' ) ) { + return; +} + +if ( ! defined( 'SAVEQUERIES' ) ) { + define( 'SAVEQUERIES', true ); +} + +// 5. Mark the Query Monitor integration as loaded. +define( 'SQLITE_QUERY_MONITOR_LOADED', true ); diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index 6ac951f3..7ce46598 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -408,6 +408,12 @@ public function prepare( $query, ...$args ) { * affected/selected for all other queries. Boolean false on error. */ public function query( $query ) { + // Query Monitor integration: + $query_monitor_active = defined( 'SQLITE_QUERY_MONITOR_LOADED' ) && SQLITE_QUERY_MONITOR_LOADED; + if ( $query_monitor_active && $this->show_errors ) { + $this->hide_errors(); + } + if ( ! $this->ready ) { return false; } @@ -427,6 +433,9 @@ public function query( $query ) { // Keep track of the last query for debug. $this->last_query = $query; + // Save the query count before running another query. + $last_query_count = count( $this->queries ?? array() ); + /* * @TODO: WPDB uses "$this->check_current_query" to check table/column * charset and strip all invalid characters from the query. @@ -478,6 +487,28 @@ public function query( $query ) { $return_val = $num_rows; } + // Query monitor integration: + if ( $query_monitor_active && class_exists( 'QM_Backtrace' ) ) { + if ( did_action( 'qm/cease' ) ) { + $this->queries = array(); + } + + $i = $last_query_count; + if ( ! isset( $this->queries[ $i ] ) ) { + return $return_val; + } + + $this->queries[ $i ]['trace'] = new QM_Backtrace(); + if ( ! isset( $this->queries[ $i ][3] ) ) { + $this->queries[ $i ][3] = $this->time_start; + } + + if ( $this->last_error && ! $this->suppress_errors ) { + $this->queries[ $i ]['result'] = new WP_Error( 'qmdb', $this->last_error ); + } else { + $this->queries[ $i ]['result'] = (int) $return_val; + } + } return $return_val; } diff --git a/wp-includes/sqlite/db.php b/wp-includes/sqlite/db.php index e60e5f02..23a7213e 100644 --- a/wp-includes/sqlite/db.php +++ b/wp-includes/sqlite/db.php @@ -67,4 +67,7 @@ $GLOBALS['wpdb'] = new WP_SQLite_Crosscheck_DB( DB_NAME ); } else { $GLOBALS['wpdb'] = new WP_SQLite_DB( defined( 'DB_NAME' ) ? DB_NAME : '' ); + + // Boot the Query Monitor plugin if it is active. + require_once dirname( __DIR__, 2 ) . '/integrations/query-monitor/boot.php'; } From d20bacc8ba44be88a89c65f9507e14e98b70a503 Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Tue, 8 Jul 2025 14:49:49 +0200 Subject: [PATCH 4/5] Render SQLite queries in Query Monitor debug panel --- integrations/query-monitor/plugin.php | 89 +++++++++++++++++++++++ load.php | 5 ++ wp-includes/sqlite/class-wp-sqlite-db.php | 7 ++ 3 files changed, 101 insertions(+) create mode 100644 integrations/query-monitor/plugin.php diff --git a/integrations/query-monitor/plugin.php b/integrations/query-monitor/plugin.php new file mode 100644 index 00000000..e9b235b2 --- /dev/null +++ b/integrations/query-monitor/plugin.php @@ -0,0 +1,89 @@ + $row The row data. + * @param array $cols The column names. + * @return void + */ + protected function output_query_row( array $row, array $cols ) { + // Capture the query row HTML. + ob_start(); + parent::output_query_row( $row, $cols ); + $data = ob_get_length() > 0 ? ob_get_clean() : ''; + + // Get the corresponding SQLite queries. + global $wpdb; + static $query_index = 0; + $sqlite_queries = $wpdb->queries[ $query_index ]['sqlite_queries'] ?? array(); + $sqlite_query_count = count( $sqlite_queries ); + $query_index += 1; + + // Build the SQLite info HTML. + $sqlite_info = sprintf( + '
Executed %d SQLite %s:
', + $sqlite_query_count, + 1 === $sqlite_query_count ? 'Query' : 'Queries' + ); + $sqlite_info .= '
    '; + foreach ( $sqlite_queries as $query ) { + $sqlite_info .= '
  1. '; + $sqlite_info .= '' . str_replace( '
    ', '', self::format_sql( $query['sql'] ) ) . '
    '; + $sqlite_info .= '
  2. '; + } + $sqlite_info .= '
'; + + // Inject toggle button and SQLite info into the query row HTML. + $toggle_button = ''; + $toggle_content = sprintf( '', $sqlite_info ); + + $data = str_replace( 'qm-row-sql', 'qm-row-sql qm-has-toggle', $data ); + $data = preg_replace( + '/()(.*?)(<\/td>)/s', + implode( + array( + '$1', + str_replace( '$', '\\$', $toggle_button ), + '$2', + str_replace( '$', '\\$', $toggle_content ), + '$3', + ) + ), + $data + ); + echo $data; + } +} + +// Remove the default Query Monitor class and replace it with the custom one. +remove_filter( 'qm/outputter/html', 'register_qm_output_html_db_queries', 20 ); + +/** + * Register the custom HTML output class. + * + * @param array $output + * @param QM_Collectors $collectors + * @return array + */ +function register_sqlite_qm_output_html_db_queries( array $output, $collectors ) { + $collector = QM_Collectors::get( 'db_queries' ); + if ( $collector ) { + $output['db_queries'] = new SQLite_QM_Output_Html_DB_Queries( $collector ); + } + return $output; +} + +add_filter( 'qm/outputter/html', 'register_sqlite_qm_output_html_db_queries', 20, 2 ); diff --git a/load.php b/load.php index 29f1dcef..d94bd95b 100644 --- a/load.php +++ b/load.php @@ -26,3 +26,8 @@ require_once __DIR__ . '/deactivate.php'; require_once __DIR__ . '/admin-notices.php'; require_once __DIR__ . '/health-check.php'; + +// Query Monitor integration: +if ( defined( 'SQLITE_QUERY_MONITOR_LOADED' ) && SQLITE_QUERY_MONITOR_LOADED ) { + require_once __DIR__ . '/integrations/query-monitor/plugin.php'; +} diff --git a/wp-includes/sqlite/class-wp-sqlite-db.php b/wp-includes/sqlite/class-wp-sqlite-db.php index 7ce46598..d8eca0bb 100644 --- a/wp-includes/sqlite/class-wp-sqlite-db.php +++ b/wp-includes/sqlite/class-wp-sqlite-db.php @@ -508,6 +508,13 @@ public function query( $query ) { } else { $this->queries[ $i ]['result'] = (int) $return_val; } + + // Add SQLite query data. + if ( $this->dbh instanceof WP_SQLite_Driver ) { + $this->queries[ $i ]['sqlite_queries'] = $this->dbh->get_last_sqlite_queries(); + } else { + $this->queries[ $i ]['sqlite_queries'] = $this->dbh->executed_sqlite_queries; + } } return $return_val; } From 5c2265e3343601dc3da01e12180396f540d3a9cc Mon Sep 17 00:00:00 2001 From: Jan Jakes Date: Wed, 9 Jul 2025 14:12:25 +0200 Subject: [PATCH 5/5] Support the Query Monitor integration in Playground --- integrations/query-monitor/boot.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/integrations/query-monitor/boot.php b/integrations/query-monitor/boot.php index 35ec433f..928c3413 100644 --- a/integrations/query-monitor/boot.php +++ b/integrations/query-monitor/boot.php @@ -12,6 +12,15 @@ * See: https://github.com/johnbillion/query-monitor/blob/develop/wp-content/db.php */ +/* + * In Playground, the SQLite plugin is preloaded without using the "db.php" file. + * To prevent Query Monitor from injecting its own "db.php" file, we need to set + * the "QM_DB_SYMLINK" constant to "false". + */ +if ( ! defined( 'QM_DB_SYMLINK' ) ) { + define( 'QM_DB_SYMLINK', false ); +} + // 1. Check if we should load Query Monitor (as per the original "db.php" file). if ( ! defined( 'ABSPATH' ) ) { exit;