get_charset_collate(); $categories = Tables::categories(); $campaigns = Tables::campaigns(); $codes = Tables::codes(); $redeem = Tables::redemptions(); // dbDelta needs two spaces after PRIMARY KEY and one column per line. dbDelta( "CREATE TABLE {$categories} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, slug varchar(100) NOT NULL, name varchar(190) NOT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY slug (slug) ) {$charset}; CREATE TABLE {$campaigns} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, slug varchar(100) NOT NULL, name varchar(190) NOT NULL, category_id bigint(20) unsigned DEFAULT NULL, type varchar(20) NOT NULL DEFAULT 'personal', discount_type varchar(20) NOT NULL DEFAULT 'percent', amount decimal(19,4) NOT NULL DEFAULT 0, starts_on date DEFAULT NULL, expires_on date DEFAULT NULL, valid_days int(10) unsigned DEFAULT NULL, code_prefix varchar(20) NOT NULL DEFAULT '', max_codes int(10) unsigned DEFAULT NULL, uses_per_code int(10) unsigned NOT NULL DEFAULT 1, max_redemptions int(10) unsigned DEFAULT NULL, max_per_email int(10) unsigned DEFAULT NULL, restrictions longtext DEFAULT NULL, email_restricted tinyint(1) NOT NULL DEFAULT 1, extensions longtext DEFAULT NULL, status varchar(20) NOT NULL DEFAULT 'active', created_at datetime NOT NULL, updated_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY slug (slug), KEY category_id (category_id), KEY status (status) ) {$charset}; CREATE TABLE {$codes} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, campaign_id bigint(20) unsigned NOT NULL, code varchar(40) NOT NULL, email varchar(190) NOT NULL DEFAULT '', source varchar(60) NOT NULL DEFAULT '', uses int(10) unsigned NOT NULL DEFAULT 0, expires_at datetime DEFAULT NULL, created_at datetime NOT NULL, PRIMARY KEY (id), UNIQUE KEY code (code), UNIQUE KEY campaign_email (campaign_id,email), KEY created_at (created_at) ) {$charset}; CREATE TABLE {$redeem} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, code_id bigint(20) unsigned NOT NULL, campaign_id bigint(20) unsigned NOT NULL, order_id bigint(20) unsigned NOT NULL, email varchar(190) NOT NULL DEFAULT '', country varchar(2) NOT NULL DEFAULT '', discount_amount decimal(19,4) NOT NULL DEFAULT 0, order_total decimal(19,4) NOT NULL DEFAULT 0, redeemed_at datetime NOT NULL, released_at datetime DEFAULT NULL, PRIMARY KEY (id), KEY code_order (code_id,order_id), KEY code_email (code_id,email), KEY campaign_redeemed (campaign_id,redeemed_at), KEY redeemed_at (redeemed_at), KEY order_id (order_id) ) {$charset};" ); self::migrate_restrictions(); update_option( self::SCHEMA_OPTION, self::SCHEMA_VERSION, false ); } public static function maybe_upgrade(): void { if ( get_option( self::SCHEMA_OPTION ) !== self::SCHEMA_VERSION ) { self::install(); } } /** * Schema 2.0.0 kept the minimum amount and product/category limits in their own columns. * From 2.1.0 they live in the restrictions JSON with the other rules; move them and drop the * old columns. Safe to run again: it does nothing once the old columns are gone. */ private static function migrate_restrictions(): void { global $wpdb; $table = Tables::campaigns(); $columns = (array) $wpdb->get_col( $wpdb->prepare( 'SHOW COLUMNS FROM %i', $table ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Schema check on the plugin's own table. if ( ! in_array( 'product_ids', $columns, true ) ) { return; } // phpcs:disable WordPress.DB.DirectDatabaseQuery -- One-time migration of the plugin's own table. $rows = (array) $wpdb->get_results( $wpdb->prepare( 'SELECT id, minimum_amount, product_ids, category_ids, restrictions FROM %i', $table ) ); foreach ( $rows as $row ) { $rules = Restrictions::from_array( (string) $row->restrictions ); $rules->lists['products']['include'] = Campaign::id_list( (string) $row->product_ids ); $rules->lists['categories']['include'] = Campaign::id_list( (string) $row->category_ids ); $rules->minimum_amount = null !== $row->minimum_amount ? (float) $row->minimum_amount : null; $wpdb->update( $table, array( 'restrictions' => wp_json_encode( $rules->to_array() ) ), array( 'id' => (int) $row->id ) ); } $wpdb->query( $wpdb->prepare( 'ALTER TABLE %i DROP COLUMN minimum_amount, DROP COLUMN product_ids, DROP COLUMN category_ids', $table ) ); // phpcs:enable } /** * Schema 1.x only ever existed on development sites before the first release, so it is replaced * rather than migrated. * * @param string $version Stored schema version. */ private static function drop_prerelease_schema( string $version ): void { global $wpdb; if ( 0 !== strpos( $version, '1.' ) ) { return; } foreach ( array( 'cnwscoupons_series', 'cnwscoupons_codes', 'cnwscoupons_redemptions' ) as $table ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- Pre-release tables owned by this plugin. $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $wpdb->prefix . $table ) ); } } }