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 c89f4ddc..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;
+ }
+ }
+
?>
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/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 .= '';
+ $sqlite_info .= '' . str_replace( ' ', '', self::format_sql( $query['sql'] ) ) . '
';
+ $sqlite_info .= ' ';
+ }
+ $sqlite_info .= ' ';
+
+ // Inject toggle button and SQLite info into the query row HTML.
+ $toggle_button = '+ ';
+ $toggle_content = sprintf( '%s
', $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 6ac951f3..d8eca0bb 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,35 @@ 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;
+ }
+
+ // 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;
}
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';
}