$ext_info ) {
if ( ! empty( $ext_info['tables'] ) && is_array( $ext_info['tables'] ) ) {
static::$cache_extensions_detected_issues[ $ext_slug ]['tables_db_info'] = array_fill_keys( $ext_info['tables'], false );
}
}
}
}
/**
* Get array of database information.
*
* @return array
*/
public static function get_database_info() { // phpcs:ignore -- NOSONAR - complex function.
global $wpdb;
static::init_data();
$tables = array();
$database_size = array();
if ( defined( 'DB_NAME' ) ) {
$database_table_information = $wpdb->get_results( // phpcs:ignore -- NOSONAR - custom query.
$wpdb->prepare(
"SELECT
table_name AS 'name',
engine AS 'engine',
round( ( data_length / 1024 / 1024 ), 2 ) 'data',
round( ( index_length / 1024 / 1024 ), 2 ) 'index'
FROM information_schema.TABLES
WHERE table_schema = %s
ORDER BY name ASC;",
DB_NAME
)
);
// WC Core tables to check existence of.
$core_tables = MainWP_DB::instance()->get_core_tables();
$tables = array(
'core' => array_fill_keys( $core_tables, false ),
'other' => array(),
);
$database_size = array(
'data' => 0,
'index' => 0,
);
$site_tables_prefix = $wpdb->get_blog_prefix( get_current_blog_id() );
$mainwp_tables_prefix = $site_tables_prefix . 'mainwp';
foreach ( $database_table_information as $table ) {
// to filter mainwp tables.
if ( 0 !== strpos( $table->name, $mainwp_tables_prefix ) ) {
continue;
}
$table_type = in_array( $table->name, $core_tables, true ) ? 'core' : 'other';
$tables[ $table_type ][ $table->name ] = array(
'data' => $table->data,
'index' => $table->index,
'engine' => $table->engine,
);
$database_size['data'] += $table->data;
$database_size['index'] += $table->index;
}
}
// Return database info.
return array(
'mainwp_database_version' => get_site_option( 'mainwp_db_version' ),
'database_prefix' => $wpdb->prefix,
'database_size' => $database_size,
'database_tables' => $tables,
);
}
/**
* Get array of database privileges.
*
* @return array
*/
public static function get_database_privileges() {
global $wpdb;
$database_name = $wpdb->dbname;
$required_privs = array( 'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP', 'INDEX' );
$grants = $wpdb->get_col( 'SHOW GRANTS' ); // phpcs:ignore -- NOSONAR - requires custom query.
$granted_privileges = array();
foreach ( $grants as $grant ) {
if (
! preg_match(
'/GRANT\s+(.+?)\s+ON\s+(.+?)\s+TO\s+/i',
$grant,
$matches
)
) {
continue;
}
$privileges = strtoupper( trim( $matches[1] ) );
$scope = trim( $matches[2] );
// Global privileges.
if ( '*.*' === $scope ) {
if ( false !== strpos( $privileges, 'ALL PRIVILEGES' ) ) {
$granted_privileges = $required_privs;
break;
}
$granted_privileges = array_merge(
$granted_privileges,
array_map( 'trim', explode( ',', $privileges ) )
);
continue;
}
// Database-specific privileges.
if ( preg_match( '/^`([^`]+)`\.\*$/', $scope, $db_matches ) ) {
$grant_db = strtr(
$db_matches[1],
array(
'\_' => '_',
'\%' => '%',
)
);
if ( $grant_db !== $database_name ) {
continue;
}
if ( false !== strpos( $privileges, 'ALL PRIVILEGES' ) ) {
$granted_privileges = $required_privs;
break;
}
$granted_privileges = array_merge(
$granted_privileges,
array_map( 'trim', explode( ',', $privileges ) )
);
}
}
$granted_privileges = array_unique( $granted_privileges );
return array(
'database_grants' => $granted_privileges,
'database_missing_privileges' => array_diff(
$required_privs,
$granted_privileges
),
);
}
/**
* Detect missing tables.
*
* @param string $table_name Table name.
*
* @return array Missing tables.
*/
public static function detect_missing_tables( $tables ) { // phpcs:ignore -- NOSONAR - complex function.
if ( ! is_array( $tables ) ) {
$tables = array();
}
$missing_tables = array(
'core' => array(),
'extensions' => array(),
);
// check mainwp tables conditional with other tables.
if ( isset( $tables['core'] ) && is_array( $tables['core'] ) ) {
foreach ( $tables['core'] as $tbl_name => $info ) {
if ( empty( $info ) ) {
$missing_tables['core'][] = esc_html( $tbl_name );
}
}
if ( ! empty( $tables['other'] ) && is_array( $tables['other'] ) ) {
static::detect_missing_other_tables( $tables['other'] );
}
if ( ! empty( static::$cache_extensions_detected_issues ) && is_array( static::$cache_extensions_detected_issues ) ) {
foreach ( static::$cache_extensions_detected_issues as $ext_slug => $tables_info ) {
if ( ! empty( $tables_info['tables_db_info'] ) && is_array( $tables_info['tables_db_info'] ) ) {
foreach ( $tables_info['tables_db_info'] as $tbl_name => $info ) {
if ( empty( $info ) ) {
if ( ! isset( $missing_tables['extensions'][ $ext_slug ] ) ) {
$missing_tables['extensions'][ $ext_slug ] = array();
}
$missing_tables['extensions'][ $ext_slug ][] = esc_html( $tbl_name );
}
}
}
}
}
}
return $missing_tables;
}
/**
* Checking other tables.
*
* @param string $table_name Table name.
*
* @return void
*/
protected static function detect_missing_other_tables( $tables ) { // phpcs:ignore -- NOSONAR - complex function.
if ( ! is_array( static::$cache_extensions_loaded_info ) ) {
static::$cache_extensions_loaded_info = array();
}
foreach ( $tables as $table_name => $info ) {
static::detect_missing_extensions_tables( $table_name, $info );
}
}
/**
* Checking missing extensions tables.
*
* @param string $table_name Table name.
* @param array $info Table info.
*
* @return void
*/
protected static function detect_missing_extensions_tables( $table_name, $info ) { // phpcs:ignore -- NOSONAR - complex function.
if ( false === strpos( $table_name, 'mainwp_' ) ) {
return;
}
if ( is_array( static::$cache_extensions_detected_issues ) && ! empty( static::$cache_extensions_detected_issues ) ) {
foreach ( static::$cache_extensions_detected_issues as $ext_slug => $ext_info ) {
if ( is_array( $ext_info ) && isset( $ext_info['tables_db_info'][ $table_name ] ) ) {
static::$cache_extensions_detected_issues[ $ext_slug ]['tables_db_info'][ $table_name ] = $info;
}
}
}
}
/**
* Method prepare_db_and_tables_issues().
*
* @param array $missing_tables Detected Missing tables.
*
* @return void
*/
public static function prepare_db_and_tables_issues( $missing_tables ) {
if ( ! is_array( $missing_tables ) ) {
$missing_tables = array();
}
$db_issues_details = '';
if ( ! empty( $missing_tables['core'] ) ) {
$db_issues_details = sprintf(
'' . esc_html__( 'Detected missing core tables', 'mainwp' ) . ': %1$s.',
esc_html( implode( ', ', $missing_tables['core'] ) )
);
}
if ( ! empty( $missing_tables['extensions'] ) ) {
foreach ( $missing_tables['extensions'] as $ext_slug => $tbls ) {
$title = is_array( static::$cache_extensions_loaded_info ) && isset( static::$cache_extensions_loaded_info[ $ext_slug ]['title'] ) ? static::$cache_extensions_loaded_info[ $ext_slug ]['title'] : esc_html__( 'other', 'mainwp' );
$title = MainWP_Extensions_Handler::polish_string_name( $title );
$db_issues_details .= sprintf(
' %1$s: %2$s.',
esc_html( $title ),
esc_html( implode( ', ', $tbls ) )
);
}
}
if ( is_array( static::$cache_extensions_detected_issues ) ) {
if ( ! empty( static::$cache_extensions_detected_issues['found_missing_columns'] ) ) {
$found_count = array_sum(
array_map( 'count', static::$cache_extensions_detected_issues['found_missing_columns'] )
);
$db_issues_details .= sprintf(
' ' . esc_html__( 'Found %1$s missing columns in tables.', 'mainwp' ) . '',
$found_count
);
}
if ( ! empty( static::$cache_extensions_detected_issues['found_invalid_columns'] ) && is_array( static::$cache_extensions_detected_issues['found_invalid_columns'] ) ) {
$invalid_count = 0;
foreach ( static::$cache_extensions_detected_issues['found_invalid_columns'] as $invalid_cols ) {
$invalid_count += count( $invalid_cols );
}
$db_issues_details .= sprintf(
' ' . esc_html__( 'Found %1$s invalid columns definitions in tables.', 'mainwp' ) . '',
$invalid_count
);
}
}
if ( ! empty( $db_issues_details ) ) {
static::$cache_detected_issues[] = array(
'severity' => 'error',
'title' => esc_html__( 'MainWP datatable Issues', 'mainwp' ),
'detail_html' => $db_issues_details,
'anchor' => 'mainwp-system-report-mainwp-database-table',
'kb_url' => 'https://docs.mainwp.com/troubleshooting/resolve-system-requirement-issues/',
);
}
}
/**
* Get detected database issues.
*
* Used by get_primary_issues().
*
* @return array Detected database issues.
*/
public static function get_detected_db_issues() {
return static::$cache_detected_issues;
}
/**
* Method detect_table_columns_issues().
*
* @param string $table_name Table name.
* @param string $type Type Table: core or other.
*
* @return array Missing and Invalid columns.
*/
public static function detect_table_columns_issues( $table_name, $type ) { //phpcs:ignore -- NOSONAR - complex function.
global $wpdb;
if ( ! is_string( $table_name ) || empty( $table_name ) ) {
return array();
}
$table_name = esc_sql( $table_name );
$actual_columns = $wpdb->get_col( // phpcs:ignore -- NOSONAR - custom query.
"SHOW COLUMNS FROM `{$table_name}`", // phpcs:ignore -- NOSONAR - escaped table name.
0
);
$missing = array();
$invalid_cols = array();
$expected_cols = array();
$expected_schemas = array();
if ( 'core' === $type ) {
$core_tbls_schema = static::get_core_db_schema_info();
if ( is_array( $core_tbls_schema ) && isset( $core_tbls_schema[ $table_name ] ) && is_array( $core_tbls_schema[ $table_name ] ) && isset( $core_tbls_schema[ $table_name ]['columns'] ) && is_array( $core_tbls_schema[ $table_name ]['columns'] ) ) {
$expected_cols = array_keys( $core_tbls_schema[ $table_name ]['columns'] );
$expected_schemas = $core_tbls_schema[ $table_name ]['columns'];
}
} elseif ( ! empty( $actual_columns ) && ! empty( static::$cache_extensions_loaded_info ) && is_array( static::$cache_extensions_loaded_info ) ) {
foreach ( static::$cache_extensions_loaded_info as $ext_info ) {
if ( ! empty( $ext_info['schema'] ) && is_array( $ext_info['schema'] ) && isset( $ext_info['schema'][ $table_name ] ) && is_array( $ext_info['schema'][ $table_name ] ) && isset( $ext_info['schema'][ $table_name ]['columns'] ) && is_array( $ext_info['schema'][ $table_name ]['columns'] ) ) {
$expected_cols = array_keys( $ext_info['schema'][ $table_name ]['columns'] );
$expected_schemas = $ext_info['schema'][ $table_name ]['columns'];
break;
}
}
}
if ( is_array( $expected_cols ) && ! empty( $expected_cols ) ) {
$missing = array_diff(
$expected_cols,
$actual_columns
);
}
if ( ! empty( $missing ) ) {
if ( ! isset( static::$cache_extensions_detected_issues['found_missing_columns'] ) ) {
static::$cache_extensions_detected_issues['found_missing_columns'] = array();
}
if ( ! isset( static::$cache_extensions_detected_issues['found_missing_columns'][ $table_name ] ) ) {
static::$cache_extensions_detected_issues['found_missing_columns'][ $table_name ] = array();
}
static::$cache_extensions_detected_issues['found_missing_columns'][ $table_name ] = $missing;
}
if ( is_array( $expected_schemas ) && ! empty( $expected_schemas ) ) {
$actual_schemas = $wpdb->get_results(
"SHOW FULL COLUMNS FROM `{$table_name}`", // phpcs:ignore -- NOSONAR - escaped table name.
ARRAY_A
);
$schemas_by_column = array_column(
$actual_schemas,
null,
'Field'
);
$invalid_cols = static::detect_invalid_column_definitions( $table_name, $expected_schemas, $schemas_by_column );
if ( ! empty( $invalid_cols ) ) {
if ( ! isset( static::$cache_extensions_detected_issues['found_invalid_columns'] ) ) {
static::$cache_extensions_detected_issues['found_invalid_columns'] = array();
}
if ( ! isset( static::$cache_extensions_detected_issues['found_invalid_columns'][ $table_name ] ) ) {
static::$cache_extensions_detected_issues['found_invalid_columns'][ $table_name ] = array();
}
static::$cache_extensions_detected_issues['found_invalid_columns'][ $table_name ] = $invalid_cols;
}
}
return array(
'missing_columns' => $missing,
'invalid_columns' => $invalid_cols,
);
}
/**
* Check for incorrect column definitions in an extension table.
*
* @param string $table_name Table name information.
* @param array $schema Table schema information.
* @param array $actual_cols_schemas Actual columns information.
*
* @return array $issues Column definitions issues.
*/
protected static function detect_invalid_column_definitions( $table_name, $columns, $actual_cols_schemas ) { // phpcs:ignore -- NOSONAR - complex function.
$issues = array();
foreach ( $columns as $column_name => $expected_definition ) {
if ( ! isset( $actual_cols_schemas[ $column_name ] ) ) {
continue; // already handled by missing column check.
}
$actual = static::get_column_definition( $actual_cols_schemas[ $column_name ] );
$expected = static::normalize_definition( $expected_definition );
$actual = static::normalize_definition( $actual );
if ( $expected !== $actual ) {
$issues[] = array(
'table_name' => $table_name,
'column' => $column_name,
'expected' => $expected,
'actual' => $actual,
);
}
}
return $issues;
}
/**
* Normalize a column definition for comparison.
*
* Explicit and implicit nullable forms, including DEFAULT NULL, compare
* as the same definition MySQL reports when the default is omitted.
*
* @param string $definition Column definition.
*
* @return string
*/
protected static function normalize_definition( $definition ) {
$definition = strtoupper( trim( $definition ) );
// Normalize whitespace.
$definition = preg_replace( '/\s+/', ' ', $definition );
// Normalize spaces inside type declarations.
$definition = preg_replace(
'/\(\s*(\d+)\s*,\s*(\d+)\s*\)/',
'($1,$2)',
$definition
);
// Normalize repeated quotes.
$definition = str_replace( "''", '""', $definition );
$definition = preg_replace(
'/DEFAULT\s+["\']{2,}/',
'DEFAULT ""',
$definition
);
// Remove integer display widths.
$definition = preg_replace(
'/\b(TINYINT|SMALLINT|MEDIUMINT|INT|BIGINT)\(\d+\)/',
'$1',
$definition
);
// Normalize NULL handling.
// MySQL reports an explicit NULL default as no default at all.
// Remove that default before the nullable marker so both forms compare equal.
// Quoted literals are skipped, including escapes and doubled quotes, so a
// default value that contains this text is left intact.
$quoted = '/(?:\'(?:\'\'|\\\\.|[^\'\\\\])*\'|"(?:\\\\.|""|[^"\\\\])*")(*SKIP)(*F)|';
$definition = preg_replace(
$quoted . '\s+DEFAULT\s+NULL\b/',
'',
$definition
);
$definition = preg_replace(
$quoted . '(?prefix . 'mainwp_';
$result = array();
foreach ( $core_schema as $key => $value ) {
$result[ $prefix . $key ] = $value;
}
$core_schema = $result;
}
if ( ! is_array( $core_schema ) ) {
$core_schema = array();
}
return $core_schema;
}
/**
* Method get_loaded_extension_title().
*
* @param string $ext_slug Extension slug.
*
* @return string Extension info title.
*/
public static function get_loaded_extension_title( $ext_slug ) {
$title = is_array( static::$cache_extensions_loaded_info ) && isset( static::$cache_extensions_loaded_info[ $ext_slug ]['title'] ) ? static::$cache_extensions_loaded_info[ $ext_slug ]['title'] : esc_html__( 'other', 'mainwp' );
return MainWP_Extensions_Handler::polish_string_name( $title );
}
}