log_db_option_key ); $rslt = $this->query( "SHOW TABLES LIKE '" . $this->table_name( 'wp_logs' ) . "'" ); if ( empty( static::num_rows( $rslt ) ) ) { $currentVersion = false; } if ( $currentVersion === $this->log_db_version ) { return; } $suppress = $this->wpdb->suppress_errors(); $this->update_log_db_60_before_dbDelta( $currentVersion ); $this->wpdb->suppress_errors( $suppress ); $charset_collate = $wpdb->get_charset_collate(); $tbl = 'CREATE TABLE ' . $this->table_name( 'wp_logs' ) . " ( log_id bigint(20) NOT NULL auto_increment, site_id bigint(20) unsigned NULL, log_type_id bigint NOT NULL DEFAULT 0, item varchar(256) NOT NULL DEFAULT '', user_id int(11) unsigned NOT NULL DEFAULT '0', user_login varchar(100) NOT NULL, action varchar(100) NOT NULL, context varchar(100) NOT NULL, connector varchar(100) NOT NULL, state tinyint(1) unsigned NULL, created BIGINT(20) UNSIGNED NOT NULL, duration float(11,4) NOT NULL DEFAULT '0', dismiss tinyint(1) NOT NULL DEFAULT 0, KEY site_id (site_id), KEY user_id (user_id), KEY user_login (user_login), KEY created (created), KEY duration (duration), KEY context (context), KEY connector (connector), KEY action (action), KEY state (state), KEY `idx_site_created` (`site_id`, `created`), KEY item (item(191))"; if ( empty( $currentVersion ) ) { $tbl .= ', PRIMARY KEY (log_id)'; } $tbl .= ') ' . $charset_collate; $sql[] = $tbl; $tbl = 'CREATE TABLE ' . $this->table_name( 'wp_logs_meta' ) . ' ( meta_id bigint(20) unsigned NOT NULL AUTO_INCREMENT, meta_log_id bigint(20) unsigned NOT NULL, meta_key varchar(200) NOT NULL, meta_value mediumtext NOT NULL, KEY meta_log_id (meta_log_id), UNIQUE KEY meta_unique (meta_log_id, meta_key(191))'; if ( empty( $currentVersion ) ) { $tbl .= ', PRIMARY KEY (`meta_id`) '; } $tbl .= ') ' . $charset_collate; $sql[] = $tbl; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; // NOSONAR - WP compatible. global $wpdb; if ( MainWP_Utility::instance()->is_disabled_functions( 'error_log' ) || ! function_exists( '\error_log' ) ) { error_reporting(0); // phpcs:ignore -- try to disabled the error_log somewhere in WP. } $suppress = $this->wpdb->suppress_errors(); foreach ( $sql as $query ) { dbDelta( $query ); } $this->update_log_db( $currentVersion ); $this->update_log_db_60( $currentVersion ); $this->wpdb->suppress_errors( $suppress ); $this->create_archive_tables( $currentVersion ); MainWP_Utility::update_option( $this->log_db_option_key, $this->log_db_version ); $wpdb->suppress_errors( $suppress ); } /** * Method update module log tables. * * @param string $currentVersion Current db version. */ public function update_log_db( $currentVersion ) { // phpcs:ignore -- NOSONAR - complex. $is_db_ver_with_archive = version_compare( $currentVersion, '1.0.1.8', '>=' ); // NOSONAR - non-ip. if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '1.0.1.9', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs' ) . ' MODIFY COLUMN item varchar(256) NOT NULL DEFAULT ""' ); //phpcs:ignore -- ok. } if ( ! empty( $currentVersion ) && $is_db_ver_with_archive && version_compare( $currentVersion, '1.0.1.10', '<' ) ) { // NOSONAR - non-ip. $this->create_archive_tables( $currentVersion, true ); $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' ADD COLUMN archived_at int(11) NOT NULL DEFAULT 0' ); //phpcs:ignore -- ok. } if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '1.0.1.11', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs' ) . ' ADD INDEX item (item(191))' ); //phpcs:ignore -- ok. } if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '1.0.1.16', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs' ) . ' DROP COLUMN object_id' ); //phpcs:ignore -- ok. if ( $is_db_ver_with_archive ) { $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' DROP INDEX created' ); //phpcs:ignore -- ok. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' DROP INDEX index_site_object_id' ); //phpcs:ignore -- ok. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' DROP COLUMN object_id' ); //phpcs:ignore -- ok. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' MODIFY COLUMN created double NOT NULL' ); //phpcs:ignore -- ok. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' ADD INDEX created ( created )' ); //phpcs:ignore -- ok. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' ADD INDEX idx_site_created(site_id, created)' ); //phpcs:ignore -- ok. } } if ( ! empty( $currentVersion ) && $is_db_ver_with_archive && version_compare( $currentVersion, '1.0.1.20', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' ADD COLUMN user_login varchar(100) NOT NULL' ); //phpcs:ignore -- ok. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' ADD INDEX user_login ( user_login )' ); //phpcs:ignore -- ok. } if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '1.0.1.26', '<' ) ) { // NOSONAR - non-ip. $count = Log_DB_Helper::instance()->count_legacy_dismissed(); if ( ! empty( $count ) ) { update_option( 'mainwp_module_logs_updates_dismissed_db_process_status', 'require_update' ); } } if ( $is_db_ver_with_archive && version_compare( $currentVersion, '1.0.1.29', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' MODIFY log_id bigint(20) NOT NULL' ); //phpcs:ignore -- ok. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_meta_archive' ) . ' MODIFY meta_id bigint(20) unsigned' ); //phpcs:ignore -- ok. } if ( version_compare( $currentVersion, '1.0.1.7', '>=' ) && version_compare( $currentVersion, '1.0.1.9', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs' ) . ' DROP INDEX state' ); //phpcs:ignore -- ok. } if ( ! empty( $currentVersion ) && $is_db_ver_with_archive && version_compare( $currentVersion, '1.0.1.35', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' ADD COLUMN log_type_id bigint NOT NULL DEFAULT 0' ); //phpcs:ignore -- ok. } } /** * Method update module log tables. * * @param string $currentVersion Current db version. */ private function update_log_db_60_before_dbDelta( $currentVersion ) { if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '1.0.1.48', '<' ) ) { // NOSONAR - non-ip. global $wpdb; $meta_table = esc_sql( $this->table_name( 'wp_logs_meta' ) ); $existing_indexes = $wpdb->get_col( "SHOW INDEX FROM {$meta_table}", 2 ); // phpcs:ignore -- ok. Column 2 is Key_name. $drop_clauses = array(); foreach ( array( 'meta_log_id_key', 'meta_key' ) as $index_name ) { if ( in_array( $index_name, $existing_indexes, true ) ) { $drop_clauses[] = 'DROP INDEX ' . esc_sql( $index_name ); } } if ( ! empty( $drop_clauses ) ) { $wpdb->query( 'ALTER TABLE ' . $meta_table . ' ' . implode( ', ', $drop_clauses ) ); // phpcs:ignore -- ok. } // Check if duplicates exist. //phpcs:ignore -- ok. $has_duplicates = $wpdb->get_var( " SELECT COUNT(*) FROM ( SELECT 1 FROM {$meta_table} GROUP BY meta_log_id, meta_key HAVING COUNT(*) > 1 ) dup " ); if ( $has_duplicates > 0 ) { // Backup duplicates first. //phpcs:ignore -- ok. $wpdb->query( "CREATE TABLE IF NOT EXISTS {$meta_table}_dup_backup LIKE {$meta_table}" ); //phpcs:ignore -- custom query ok. $wpdb->query( " INSERT IGNORE INTO {$meta_table}_dup_backup SELECT * FROM {$meta_table} WHERE (meta_log_id, meta_key) IN ( SELECT meta_log_id, meta_key FROM {$meta_table} GROUP BY meta_log_id, meta_key HAVING COUNT(*) > 1 ) " ); // Delete duplicates, keep newest (MAX meta_id). //phpcs:ignore -- ok. $wpdb->query( " DELETE t FROM {$meta_table} t JOIN ( SELECT meta_log_id, meta_key, MAX(meta_id) AS keep_id FROM {$meta_table} GROUP BY meta_log_id, meta_key HAVING COUNT(*) > 1 ) dup ON t.meta_log_id = dup.meta_log_id AND t.meta_key = dup.meta_key WHERE t.meta_id <> dup.keep_id " ); } } } /** * Method update module log tables. * * @param string $currentVersion Current db version. */ private function update_log_db_60( $currentVersion ) { $is_db_ver_with_archive = version_compare( $currentVersion, '1.0.1.8', '>=' ); // NOSONAR - non-ip. if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '1.0.1.40', '<' ) ) { // NOSONAR - non-ip. $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs' ) . ' MODIFY COLUMN created BIGINT(20) UNSIGNED NOT NULL' ); //phpcs:ignore -- ok. // to save microsecords. if ( $is_db_ver_with_archive ) { $table = esc_sql( $this->table_name( 'wp_logs' ) ); $limit = 1000; do { // Update a safe batch. $updated = $this->wpdb->query( $this->wpdb->prepare( //phpcs:ignore -- esc table name ok. " UPDATE {$table} SET created = created * 1000000 WHERE created < %d LIMIT %d ", 10000000000, $limit ) ); usleep( 100000 ); // 100ms. } while ( $updated > 0 ); } $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' MODIFY COLUMN created BIGINT(20) UNSIGNED NOT NULL' ); //phpcs:ignore -- ok. if ( $is_db_ver_with_archive ) { $table = $this->table_name( 'wp_logs_archive' ); $limit = 1000; do { // Update a safe batch. $updated = $this->wpdb->query( // phpcs:ignore -- esc table name ok. $this->wpdb->prepare( //phpcs:ignore -- escaped table name ok. " UPDATE {$table} SET created = created * 1000000 WHERE created < %d LIMIT %d ", 10000000000, $limit //phpcs:ignore -- escaped ok. ) ); usleep( 100000 ); // 100ms. } while ( $updated > 0 ); } } $this->update_changes_logs_meta_60( $currentVersion ); } /** * Method update_changes_logs_meta_60(). * * @param mixed $currentVersion * @return void */ private function update_changes_logs_meta_60( $currentVersion ) { if ( ! empty( $currentVersion ) && version_compare( $currentVersion, '1.0.1.46', '<' ) ) { // NOSONAR - non-ip. $log_table = esc_sql( $this->table_name( 'wp_logs' ) ); $meta_table = esc_sql( $this->table_name( 'wp_logs_meta' ) ); $meta_items = $this->wpdb->get_results( // phpcs:ignore -- custom escaped query ok. " SELECT m.meta_log_id, m.meta_value FROM {$log_table} l JOIN {$meta_table} m ON l.log_id = m.meta_log_id WHERE m.meta_key = 'extra_info' AND (l.context = 'plugin' OR l.context = 'theme') ", ARRAY_A ); foreach ( $meta_items as $row ) { $extra_info = json_decode( $row['meta_value'], true ); if ( ! is_array( $extra_info ) ) { continue; } $key = ''; $val = ''; if ( ! empty( $extra_info['slug'] ) ) { $key = 'slug'; $val = $extra_info['slug']; } elseif ( ! empty( $extra_info['name'] ) ) { $key = 'name'; $val = $extra_info['name']; } if ( empty( $key ) || empty( $val ) ) { continue; } $this->wpdb->replace( $meta_table, array( 'meta_log_id' => (int) $row['meta_log_id'], 'meta_key' => $key, //phpcs:ignore -- ok. 'meta_value' => $val, //phpcs:ignore -- ok. ), array( '%d', '%s', '%s' ) ); } } } /** * Method get_current_logs_db_ver(). */ public function get_current_logs_db_ver() { return get_option( $this->log_db_option_key ); } /** * Create archive_ logs tables. * * @param string $currentVersion Current db version. * @param bool $create_if_not_exist Whether to force creation of archive tables if they don't exist. */ public function create_archive_tables( $currentVersion = '', $create_if_not_exist = false ) { $archive = $this->table_name( 'wp_logs_archive' ); if ( empty( $currentVersion ) || $create_if_not_exist ) { $this->wpdb->query( 'CREATE TABLE IF NOT EXISTS ' . $archive . ' LIKE ' . $this->table_name( 'wp_logs' ) ); //phpcs:ignore -- ok. $this->wpdb->query( 'CREATE TABLE IF NOT EXISTS ' . $this->table_name( 'wp_logs_meta_archive' ) . ' LIKE ' . $this->table_name( 'wp_logs_meta' ) ); //phpcs:ignore -- ok. } if ( empty( $currentVersion ) ) { $this->wpdb->query( 'ALTER TABLE ' . $this->table_name( 'wp_logs_archive' ) . ' ADD COLUMN archived_at int(11) NOT NULL DEFAULT 0' ); //phpcs:ignore -- ok. } else { $column = $this->wpdb->get_results( // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $archive is an internal MainWP table identifier; column name is bound via prepare(). $this->wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table identifiers cannot be placeholders; $archive comes from table_name(). "SHOW COLUMNS FROM {$archive} LIKE %s", 'archived_at' ) ); if ( empty( $column ) ) { $this->wpdb->query( // phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- $archive is an internal MainWP table identifier; ALTER TABLE has no user input. "ALTER TABLE {$archive} ADD COLUMN archived_at INT(11) NOT NULL DEFAULT 0" ); } } } }