show_errors; $wpdb->hide_errors(); $schema_failures = []; try { foreach ( self::get_all() as $table ) { $wpdb->last_error = ''; $table->install(); $install_error = (string) $wpdb->last_error; $table_name = $table->get_name(); if ( ! self::table_exists( $table_name ) ) { $schema_failures[] = sprintf( '%s (table missing)', $table_name ); continue; } $missing_columns = array_diff( array_keys( $table->get_columns() ), self::table_columns( $table_name ) ); $missing_indexes = array_diff( $table->get_indexes(), self::table_indexes( $table_name ) ); if ( '' !== $install_error || [] !== $missing_columns || [] !== $missing_indexes ) { $details = []; if ( '' !== $install_error ) { $details[] = 'database error: ' . $install_error; } if ( [] !== $missing_columns ) { $details[] = 'missing columns: ' . implode( ', ', $missing_columns ); } if ( [] !== $missing_indexes ) { $details[] = 'missing indexes: ' . implode( ', ', $missing_indexes ); } $schema_failures[] = sprintf( '%s (%s)', $table_name, implode( '; ', $details ) ); } } } finally { $wpdb->show_errors( $previous_show_errors ); } if ( [] !== $schema_failures ) { throw new \RuntimeException( sprintf( /* translators: %s: database schema failure details. */ esc_html__( 'IPS Order Cart could not install the required database schema: %s', 'ips-order-cart' ), implode( '; ', array_map( 'esc_html', $schema_failures ) ) ) ); } } /** * Check an exact table name without treating SQL wildcard characters as * part of the identifier. */ private static function table_exists( $table_name ) { global $wpdb; $found = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table_name ) ) ); return $table_name === $found; } /** * Read the exact column names currently installed for a table. * * @return string[] */ private static function table_columns( $table_name ) { global $wpdb; return array_map( 'strval', (array) $wpdb->get_col( $wpdb->prepare( 'SHOW COLUMNS FROM %i', $table_name ), 0 ) ); } /** * Read the exact index names currently installed for a table. * * @return string[] */ private static function table_indexes( $table_name ) { global $wpdb; $rows = $wpdb->get_results( $wpdb->prepare( 'SHOW INDEX FROM %i', $table_name ), ARRAY_A ); $indexes = []; foreach ( (array) $rows as $row ) { if ( isset( $row['Key_name'] ) ) { $indexes[] = (string) $row['Key_name']; } } return array_values( array_unique( $indexes ) ); } /** * Classes for tables exclusively owned by IPS Order Cart. * * This unfiltered registry is the single source of truth for destructive * lifecycle operations. Third-party filtered tables are never dropped. * * @return array> */ public static function owned_table_classes() { return [ 'ips-cartgate-queue' => Cartgate_Queue::class, 'ipscmi-order-cart-cartgate-queue-meta' => Cartgate_QueueMeta::class, 'ipscmi-order-cart-checkout-flow-runs' => Checkout_FlowRuns::class, 'ipscmi-order-cart-checkout-trace-events' => Checkout_TraceEvents::class, 'ipscmi-order-cart-recovery' => CartRecoveryTable::class, ]; } /** * Resolve the current-site table names from the owned registry. * * @return string[] */ public static function owned_table_names() { $names = []; foreach ( self::owned_table_classes() as $table_class ) { $table = new $table_class(); if ( $table instanceof Database_Table ) { $names[] = $table->get_name(); } } return array_values( array_unique( $names ) ); } /** * Drop only tables explicitly owned by IPS Order Cart. */ public static function drop_tables() { global $wpdb; foreach ( array_reverse( self::owned_table_names() ) as $table_name ) { $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $table_name ) ); } } /** * Load the application Database Tables * * @return array The DatabaseTables classes to be loaded */ public static function load_includes() { $includes = self::owned_table_classes(); // Schema ownership survives a downgrade; Core does not provision the Pro table. if ( ! \IPSCMI\Order\Cart\Config\PackageProfile::from_runtime()->includes_pro() ) { unset( $includes['ipscmi-order-cart-recovery'] ); } return apply_filters( 'wooordercart/database_tables', $includes ); } /** * Get all the database tables * * @return Database_Table[] An array with all the database tables */ public static function get_all() { return parent::get_all(); } /** * Get a database table object. * * @param string $table_id The table to get * * @return Database_Table The database table class * * @throws Exception When table failed to load. */ public static function get( $table_id ) { $table = parent::get( $table_id ); if ( $table instanceof Database_Table ) { //error_log("The waitline hole table: ".print_r($table,true)); return $table; } /* translators: Database table name. */ throw new Exception( sprintf( esc_html__( "Failed to load the '%s' database table.", 'ips-order-cart' ), esc_html( $table_id ) ) ); } }