| Server IP : 80.74.154.100 / Your IP : 216.73.217.0 Web Server : Apache System : Linux marissa.metanet.ch 4.18.0-553.141.2.lve.el7h.x86_64 #1 SMP Wed Jul 8 17:20:31 UTC 2026 x86_64 User : onlineadmin ( 10487) PHP Version : 8.5.7 Disable Function : opcache_get_status MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /home/httpd/vhosts/comeonline.ch/httpdocs/wp-content/plugins/zero-spam/includes/ |
Upload File : |
<?php
/**
* Database class
*
* @package ZeroSpam
*/
namespace ZeroSpam\Includes;
// Security Note: Blocks direct access to the plugin PHP files.
defined( 'ABSPATH' ) || die();
/**
* Database class
*/
class DB {
// Current DB version.
const DB_VERSION = '1.4';
/**
* DB tables
*
* @var array $tables List of plugin database tables.
*/
public static $tables = array(
'log' => 'wpzerospam_log',
'blocked' => 'wpzerospam_blocked',
'blacklist' => 'wpzerospam_blacklist',
'api_usage' => 'wpzerospam_api_usage',
'stats_daily' => 'wpzerospam_stats_daily',
'stats_monthly' => 'wpzerospam_stats_monthly',
'network_templates' => 'wpzerospam_network_templates',
'network_audit' => 'wpzerospam_network_audit',
);
/**
* Constructor
*/
public function __construct() {
add_action( 'init', array( $this, 'update' ) );
}
/**
* Installs & updates the DB tables
*/
public function update() {
if ( self::DB_VERSION !== get_option( 'zerospam_db_version' ) ) {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$sql = array();
$sql[] = 'CREATE TABLE ' . $wpdb->prefix . self::$tables['log'] . " (
log_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
blog_id bigint(20) unsigned NOT NULL DEFAULT 1,
log_type varchar(255) NOT NULL,
user_ip varchar(39) NOT NULL,
date_recorded datetime NOT NULL,
page_url varchar(255) DEFAULT NULL,
submission_data longtext DEFAULT NULL,
country varchar(2) DEFAULT NULL,
country_name varchar(255) DEFAULT NULL,
region varchar(255) DEFAULT NULL,
region_name varchar(255) DEFAULT NULL,
city varchar(255) DEFAULT NULL,
zip varchar(10) DEFAULT NULL,
latitude varchar(255) DEFAULT NULL,
longitude varchar(255) DEFAULT NULL,
PRIMARY KEY (log_id),
KEY blog_id (blog_id),
KEY blog_date (blog_id, date_recorded)
) $charset_collate;";
$sql[] = 'CREATE TABLE ' . $wpdb->prefix . self::$tables['blocked'] . " (
blocked_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
blocked_type enum('permanent','temporary') NOT NULL DEFAULT 'temporary',
user_ip varchar(39) NOT NULL,
blocked_key varchar(255) DEFAULT NULL,
key_type enum('ip','email','username','country_code','region_code','zip', 'city') NOT NULL DEFAULT 'ip',
date_added datetime NOT NULL,
start_block datetime DEFAULT NULL,
end_block datetime DEFAULT NULL,
reason varchar(255) DEFAULT NULL,
PRIMARY KEY (blocked_id)
) $charset_collate;";
$sql[] = 'CREATE TABLE ' . $wpdb->base_prefix . self::$tables['api_usage'] . " (
usage_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
site_id bigint(20) unsigned NOT NULL DEFAULT 1,
event_type enum('api_call','cache_hit','cache_miss','error') NOT NULL DEFAULT 'api_call',
endpoint varchar(255) NOT NULL,
response_code int(11) DEFAULT NULL,
response_time_ms int(11) DEFAULT NULL,
queries_limit int(11) DEFAULT NULL,
queries_made int(11) DEFAULT NULL,
queries_remaining int(11) DEFAULT NULL,
error_message text DEFAULT NULL,
request_params text DEFAULT NULL,
date_recorded datetime NOT NULL,
hour_bucket datetime NOT NULL,
day_bucket date NOT NULL,
KEY site_date (site_id, date_recorded),
KEY site_day (site_id, day_bucket),
KEY site_hour (site_id, hour_bucket),
KEY event_type (event_type),
KEY response_code (response_code),
PRIMARY KEY (usage_id)
) $charset_collate;";
$sql[] = 'CREATE TABLE ' . $wpdb->base_prefix . self::$tables['stats_daily'] . " (
stat_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
site_id bigint(20) unsigned NOT NULL DEFAULT 1,
stat_date date NOT NULL,
total_spam_blocked int(11) NOT NULL DEFAULT 0,
spam_by_type text DEFAULT NULL,
top_countries text DEFAULT NULL,
top_ips text DEFAULT NULL,
top_log_types text DEFAULT NULL,
unique_ips int(11) NOT NULL DEFAULT 0,
date_aggregated datetime NOT NULL,
PRIMARY KEY (stat_id),
UNIQUE KEY site_date (site_id, stat_date),
KEY stat_date (stat_date)
) $charset_collate;";
$sql[] = 'CREATE TABLE ' . $wpdb->base_prefix . self::$tables['stats_monthly'] . " (
stat_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
site_id bigint(20) unsigned NOT NULL DEFAULT 1,
stat_year int(4) NOT NULL,
stat_month int(2) NOT NULL,
total_spam_blocked int(11) NOT NULL DEFAULT 0,
spam_by_type text DEFAULT NULL,
top_countries text DEFAULT NULL,
top_ips text DEFAULT NULL,
top_log_types text DEFAULT NULL,
unique_ips int(11) NOT NULL DEFAULT 0,
date_aggregated datetime NOT NULL,
PRIMARY KEY (stat_id),
UNIQUE KEY site_month (site_id, stat_year, stat_month),
KEY stat_period (stat_year, stat_month)
) $charset_collate;";
$sql[] = 'CREATE TABLE ' . $wpdb->base_prefix . self::$tables['network_templates'] . " (
template_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
template_name varchar(255) NOT NULL,
template_slug varchar(255) NOT NULL,
template_type enum('built_in','custom') NOT NULL DEFAULT 'custom',
settings longtext NOT NULL,
description text DEFAULT NULL,
created_by bigint(20) unsigned NOT NULL,
created_at datetime NOT NULL,
updated_at datetime NOT NULL,
PRIMARY KEY (template_id),
UNIQUE KEY template_slug (template_slug),
KEY template_type (template_type)
) $charset_collate;";
$sql[] = 'CREATE TABLE ' . $wpdb->base_prefix . self::$tables['network_audit'] . " (
audit_id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
site_id bigint(20) unsigned NOT NULL DEFAULT 0,
action_type enum('set','lock','unlock','apply','bulk','template','import','reset') NOT NULL DEFAULT 'set',
setting_key varchar(255) DEFAULT NULL,
old_value text DEFAULT NULL,
new_value text DEFAULT NULL,
affected_sites text DEFAULT NULL,
user_id bigint(20) unsigned NOT NULL,
user_login varchar(60) NOT NULL,
ip_address varchar(39) NOT NULL,
date_created datetime NOT NULL,
PRIMARY KEY (audit_id),
KEY site_id (site_id),
KEY action_type (action_type),
KEY setting_key (setting_key),
KEY user_id (user_id),
KEY date_created (date_created)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
// Add indexes to existing log table for performance.
self::add_log_indexes();
// Migrate existing log tables to include blog_id column.
self::migrate_log_table_blog_id();
update_option( 'zerospam_db_version', self::DB_VERSION );
}
}
/**
* Add indexes to log table for performance
*/
private static function add_log_indexes() {
global $wpdb;
$table_name = $wpdb->prefix . self::$tables['log'];
// Check if indexes already exist before adding.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$indexes = $wpdb->get_results( "SHOW INDEX FROM {$table_name}", ARRAY_A );
$existing_indexes = array();
if ( $indexes ) {
foreach ( $indexes as $index ) {
$existing_indexes[] = $index['Key_name'];
}
}
// Add date_recorded index if not exists.
if ( ! in_array( 'date_recorded', $existing_indexes, true ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "ALTER TABLE {$table_name} ADD INDEX date_recorded (date_recorded)" );
}
// Add log_type index if not exists.
if ( ! in_array( 'log_type', $existing_indexes, true ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "ALTER TABLE {$table_name} ADD INDEX log_type (log_type)" );
}
// Add user_ip index if not exists.
if ( ! in_array( 'user_ip', $existing_indexes, true ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "ALTER TABLE {$table_name} ADD INDEX user_ip (user_ip)" );
}
// Add country index if not exists.
if ( ! in_array( 'country', $existing_indexes, true ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "ALTER TABLE {$table_name} ADD INDEX country (country)" );
}
}
/**
* Migrate existing log table to include blog_id column
*
* Adds blog_id column to existing log tables for compatibility with
* dashboard widget queries. Sets default value of 1 for single-site
* installations and actual blog ID for multisite.
*/
private static function migrate_log_table_blog_id() {
global $wpdb;
$table_name = $wpdb->prefix . self::$tables['log'];
// Check if table exists.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$table_exists = $wpdb->get_var( "SHOW TABLES LIKE '{$table_name}'" );
if ( ! $table_exists ) {
return; // Table doesn't exist yet, will be created with blog_id column.
}
// Check if blog_id column already exists.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$columns = $wpdb->get_results( "SHOW COLUMNS FROM {$table_name}", ARRAY_A );
$has_blog_id = false;
if ( $columns ) {
foreach ( $columns as $column ) {
if ( 'blog_id' === $column['Field'] ) {
$has_blog_id = true;
break;
}
}
}
if ( $has_blog_id ) {
return; // Column already exists.
}
// Add blog_id column with default value.
$blog_id = get_current_blog_id();
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query(
"ALTER TABLE {$table_name}
ADD COLUMN blog_id bigint(20) unsigned NOT NULL DEFAULT {$blog_id} AFTER log_id"
);
// Add indexes for blog_id.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$indexes = $wpdb->get_results( "SHOW INDEX FROM {$table_name}", ARRAY_A );
$existing_indexes = array();
if ( $indexes ) {
foreach ( $indexes as $index ) {
$existing_indexes[] = $index['Key_name'];
}
}
// Add blog_id index if not exists.
if ( ! in_array( 'blog_id', $existing_indexes, true ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "ALTER TABLE {$table_name} ADD INDEX blog_id (blog_id)" );
}
// Add composite blog_date index if not exists.
if ( ! in_array( 'blog_date', $existing_indexes, true ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
$wpdb->query( "ALTER TABLE {$table_name} ADD INDEX blog_date (blog_id, date_recorded)" );
}
}
/**
* Returns all blocked IP addresses
*/
public static function get_blocked() {
global $wpdb;
// @codingStandardsIgnoreLine
return $wpdb->get_results( 'SELECT * FROM ' . $wpdb->prefix . self::$tables['blocked'], ARRAY_A );
}
/**
* Adds/returns a blocked IP
*
* @param array $record Record to add into the database.
* @param boolean|string $key_type Type of record entry to add.
*/
public static function blocked( $record, $key_type = false ) {
global $wpdb;
if ( is_array( $record ) ) {
$blocked = false;
// Add or update a record.
if ( ! empty( $record['blocked_id'] ) ) {
// Update a record.
$blocked['blocked_id'] = $record['blocked_id'];
} elseif ( ! empty( $record['user_ip'] ) ) {
// Add a record, but first check if IP is unique.
$blocked = self::blocked( $record['user_ip'] );
} elseif ( ! empty( $record['key_type'] ) && ! empty( $record['blocked_key'] ) ) {
// Add a record, but first check if key is unique.
$blocked = self::blocked( $record['blocked_key'], $record['key_type'] );
}
if ( $blocked ) {
// Update the record.
$record['date_added'] = current_time( 'mysql' );
// @codingStandardsIgnoreLine
return $wpdb->update(
$wpdb->prefix . self::$tables['blocked'],
$record,
array(
'blocked_id' => $blocked['blocked_id'],
)
);
} else {
// Insert the record.
$record['date_added'] = current_time( 'mysql' );
// @codingStandardsIgnoreLine
return $wpdb->insert( $wpdb->prefix . self::$tables['blocked'], $record );
}
} elseif ( $key_type ) {
// Get record by key.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_row(
$wpdb->prepare(
'SELECT * FROM ' . $wpdb->prefix . self::$tables['blocked'] . ' WHERE key_type = %s AND blocked_key = %s',
$key_type,
$record
),
ARRAY_A
);
} elseif ( is_int( $record ) ) {
// Get record by ID.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_row(
$wpdb->prepare(
'SELECT * FROM ' . $wpdb->prefix . self::$tables['blocked'] . ' WHERE blocked_id = %d',
$record
),
ARRAY_A
);
} elseif ( rest_is_ip_address( $record ) ) {
// Get record by IP.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_row(
$wpdb->prepare(
'SELECT * FROM ' . $wpdb->prefix . self::$tables['blocked'] . ' WHERE user_ip = %s',
$record
),
ARRAY_A
);
}
return false;
}
/**
* Log
*
* @param string $type Type of log.
* @param array $details Array of details for the log entry.
*/
public static function log( $type, $details ) {
global $wpdb;
$log_table = $wpdb->prefix . self::$tables['log'];
// Bail early if the log table does not exist to prevent DB errors.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $log_table ) ) !== $log_table ) {
return false;
}
$page_url = \ZeroSpam\Core\Utilities::current_url();
$extension = substr( $page_url, strrpos( $page_url, '.' ) + 1 );
$ignore = array( 'map', 'js', 'css', 'ico' );
if ( in_array( $extension, $ignore, true ) ) {
// Ignore assets.
return false;
}
/**
* Check the total number of entries and delete the oldest if the maximum
* has been reached.
*/
// @codingStandardsIgnoreLine
$total_entries = $wpdb->get_var( "SELECT COUNT(*) FROM $log_table" );
$maximum_entries = \ZeroSpam\Core\Settings::get_settings( 'max_logs' );
if ( $total_entries > $maximum_entries ) {
$difference = absint( $total_entries - $maximum_entries );
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
$wpdb->query( $wpdb->prepare( "DELETE FROM {$log_table} ORDER BY date_recorded ASC LIMIT %d", $difference ) );
}
// Sanitize details array.
$details = \ZeroSpam\Core\Utilities::sanitize_array( $details );
$record = array(
'user_ip' => \ZeroSpam\Core\User::get_ip(),
'log_type' => sanitize_text_field( $type ),
'date_recorded' => current_time( 'mysql' ),
'page_url' => $page_url,
'submission_data' => wp_json_encode( $details ),
);
$record = apply_filters( 'zerospam_log_record', $record );
return $wpdb->insert( $wpdb->prefix . self::$tables['log'], $record );
}
/**
* Delete a record
*
* @param string $table Database table key.
* @param string $key Database record key.
* @param string $value Database record value.
*/
public static function delete( $table, $key, $value ) {
global $wpdb;
// @codingStandardsIgnoreLine
$wpdb->delete(
$wpdb->prefix . self::$tables[ $table ],
array(
$key => $value,
)
);
}
/**
* Delete everything in a table
*
* @param string $table Database table to truncate.
*/
public static function delete_all( $table ) {
global $wpdb;
// @codingStandardsIgnoreLine
$wpdb->query( "TRUNCATE TABLE " . $wpdb->prefix . self::$tables[ $table ] );
}
/**
* Query the DB
*
* @param string $table Database table to query.
* @param array $args Arguments for the select statement.
*/
public static function query( $table, $args = array() ) {
global $wpdb;
if ( ! array_key_exists( $table, self::$tables ) ) {
return false;
}
// Bail early if the table does not exist to prevent DB errors.
$full_table = $wpdb->prefix . self::$tables[ $table ];
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $full_table ) ) !== $full_table ) {
return false;
}
// Allowed columns for SELECT — prevents injection via column names.
$allowed_columns = array( '*', 'log_id', 'blog_id', 'log_type', 'user_ip', 'date_recorded', 'page_url',
'submission_data', 'country', 'country_name', 'region', 'region_name', 'city', 'zip',
'latitude', 'longitude', 'blocked_id', 'blocked_type', 'blocked_key', 'key_type',
'date_added', 'start_block', 'end_block', 'reason', 'COUNT(*)',
);
$sql = 'SELECT';
if ( ! empty( $args['select'] ) ) {
$safe_columns = array();
foreach ( $args['select'] as $col ) {
$col = trim( $col );
if ( in_array( $col, $allowed_columns, true ) ) {
$safe_columns[] = $col;
}
}
$sql .= ' ' . ( ! empty( $safe_columns ) ? implode( ', ', $safe_columns ) : '*' ) . ' ';
} else {
$sql .= ' * ';
}
$sql .= 'FROM ' . $wpdb->prefix . self::$tables[ $table ];
// Build WHERE clause using prepared statements.
$prepare_values = array();
if ( ! empty( $args['where'] ) ) {
$where_clauses = array();
foreach ( $args['where'] as $key => $where ) {
// Sanitize column name — only allow alphanumeric and underscores.
$column = preg_replace( '/[^a-zA-Z0-9_]/', '', $key );
// Determine the comparison operator.
$relation = '=';
$allowed_relations = array( '=', '!=', '>', '<', '>=', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN' );
if ( ! empty( $where['relation'] ) && in_array( strtoupper( trim( $where['relation'] ) ), $allowed_relations, true ) ) {
$relation = strtoupper( trim( $where['relation'] ) );
}
if ( is_array( $where['value'] ) ) {
$placeholders = array_fill( 0, count( $where['value'] ), '%s' );
$where_clauses[] = $column . ' IN (' . implode( ', ', $placeholders ) . ')';
$prepare_values = array_merge( $prepare_values, $where['value'] );
} elseif ( is_numeric( $where['value'] ) ) {
$where_clauses[] = $column . ' ' . $relation . ' %d';
$prepare_values[] = $where['value'];
} else {
$where_clauses[] = $column . ' ' . $relation . ' %s';
$prepare_values[] = $where['value'];
}
}
if ( ! empty( $where_clauses ) ) {
$sql .= ' WHERE ' . implode( ' AND ', $where_clauses );
}
}
if ( ! empty( $args['orderby'] ) ) {
$orderby = $args['orderby'];
if ( ! empty( $args['order'] ) ) {
$orderby .= ' ' . $args['order'];
}
$sanitized_orderby = sanitize_sql_orderby( $orderby );
if ( $sanitized_orderby ) {
$sql .= ' ORDER BY ' . $sanitized_orderby;
}
}
if ( ! empty( $args['limit'] ) ) {
$sql .= ' LIMIT ' . absint( $args['limit'] );
}
if ( ! empty( $args['offset'] ) ) {
$sql .= ' OFFSET ' . absint( $args['offset'] );
}
// Use prepared statement if we have values to bind.
if ( ! empty( $prepare_values ) ) {
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_results( $wpdb->prepare( $sql, $prepare_values ), ARRAY_A );
}
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
return $wpdb->get_results( $sql, ARRAY_A );
}
}