get_charset_collate(); $forms = Schema::table( self::FORMS ); $entries = Schema::table( self::ENTRIES ); dbDelta( "CREATE TABLE {$forms} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL, definition longtext NOT NULL, created_at datetime NOT NULL, updated_at datetime NOT NULL, PRIMARY KEY (id) ) {$charset};" ); dbDelta( "CREATE TABLE {$entries} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, form_id bigint(20) unsigned NOT NULL, data longtext NOT NULL, is_read tinyint(1) unsigned NOT NULL DEFAULT 0, mail_status varchar(10) NOT NULL DEFAULT 'none', mail_error text NULL, email varchar(190) NOT NULL DEFAULT '', created_at datetime NOT NULL, PRIMARY KEY (id), KEY form_created (form_id,created_at), KEY email (email), KEY mail_status (mail_status) ) {$charset};" ); } /** * Drops both tables. */ public static function drop(): void { global $wpdb; foreach ( array( self::ENTRIES, self::FORMS ) as $name ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange -- The app removes its own tables. $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', Schema::table( $name ) ) ); } } /** * Saves a new form. * * @param string $title Title. * @param FormDefinition $definition Definition. * @return int Form id. */ public function create( string $title, FormDefinition $definition ): int { global $wpdb; $now = current_time( 'mysql', true ); $wpdb->insert( Schema::table( self::FORMS ), array( 'title' => mb_substr( $title, 0, 200 ), 'definition' => (string) wp_json_encode( $definition->to_array() ), 'created_at' => $now, 'updated_at' => $now, ) ); return (int) $wpdb->insert_id; } /** * Changes a form. * * @param int $id Form id. * @param string $title Title. * @param FormDefinition $definition Definition. */ public function update( int $id, string $title, FormDefinition $definition ): bool { global $wpdb; return false !== $wpdb->update( Schema::table( self::FORMS ), array( 'title' => mb_substr( $title, 0, 200 ), 'definition' => (string) wp_json_encode( $definition->to_array() ), 'updated_at' => current_time( 'mysql', true ), ), array( 'id' => $id ) ); } /** * Deletes a form and its entries. * * @param int $id Form id. */ public function delete( int $id ): void { global $wpdb; $entries = $wpdb->get_col( $wpdb->prepare( 'SELECT id FROM %i WHERE form_id = %d', Schema::table( self::ENTRIES ), $id ) ); $wpdb->delete( Schema::table( self::ENTRIES ), array( 'form_id' => $id ) ); $wpdb->delete( Schema::table( self::FORMS ), array( 'id' => $id ) ); foreach ( is_array( $entries ) ? $entries : array() as $entry ) { /** This action is documented in EntryStore::delete(). */ do_action( 'forensix_form_entry_deleted', (int) $entry ); } } /** * One form. * * @param int $id Form id. * @return array{id: int, title: string, definition: FormDefinition}|null */ public function find( int $id ): ?array { global $wpdb; $row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', Schema::table( self::FORMS ), $id ), ARRAY_A ); return is_array( $row ) ? self::row( $row ) : null; } /** * All forms, newest first. * * @return array */ public function all(): array { global $wpdb; $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC', Schema::table( self::FORMS ) ), ARRAY_A ); return array_map( array( self::class, 'row' ), is_array( $rows ) ? $rows : array() ); } /** * Builds the result of a row. * * @param array $row Table row. * @phpstan-param array $row * @return array{id: int, title: string, definition: FormDefinition} */ private static function row( array $row ): array { $data = json_decode( (string) $row['definition'], true ); return array( 'id' => (int) $row['id'], 'title' => (string) $row['title'], 'definition' => new FormDefinition( is_array( $data ) ? $data : array() ), ); } }