self::VERSION, 'fields' => [], 'groups' => [], 'mail' => self::mail_defaults(), 'mail_2' => [ 'active' => false ] + self::mail_defaults(), 'messages' => [], 'settings' => [ 'style' => 'default', 'store_entries' => true, 'redirect' => '', // The success message fades out after a few seconds (form.js) 'hide_success' => false, 'class' => '', // Browser autofill of the fields 'autocomplete' => true, // Fields are checked, nothing is sent or saved, the visitor sees "sent" 'demo' => false, // No mail; the entry is still saved 'skip_mail' => false, // Shown and accepted for logged-in users only 'subscribers_only' => false, // The captcha it runs (Spam\Captcha::of()): '' those Settings → Spam runs on every form, 'none', or one by its ID 'captcha' => '', // Addresses each message is posted to as JSON (Integrations\Webhooks) 'webhooks' => [], // The CSS and JavaScript tabs: "selector" is this form's wrapper 'css' => '', 'js' => '', ], // The values of the editor's tabs of add-ons: [ tab => values ] (Addons\Addons::form_tabs()) 'addons' => Addons::form_defaults(), ]; return (array) apply_filters( 'remaily_config_defaults', $defaults ); } /** * Defaults of one mail * * @return array */ public static function mail_defaults() { return [ 'to' => '[_site_admin_email]', 'from' => '[_site_title] <' . self::from_address() . '>', 'subject' => '[_site_title]', 'headers' => '', 'body' => '', 'html' => true, 'attachments' => '', ]; } /** * An address on the site's own domain, the one mail servers accept as sender * * @return string */ public static function from_address() { $host = (string) wp_parse_url( network_home_url(), PHP_URL_HOST ); $host = preg_replace( '/^www\./', '', $host ); return 'wordpress@' . ( $host ?: 'localhost' ); } /** * A form's settings, upgraded and merged with the defaults * * @param int $post_id Form ID * * @return array */ public static function get( $post_id ) { $stored = json_decode( (string) get_post_meta( $post_id, self::META, true ), true ); return self::merge( self::defaults(), self::upgrade( is_array( $stored ) ? $stored : [] ) ); } /** * Save a form's settings * * @param int $post_id Form ID * @param array $config Settings */ public static function save( $post_id, array $config ) { // Webhooks send visitors' messages to other servers: only those who manage the site set them if ( !current_user_can( 'manage_options' ) ) { $config['settings']['webhooks'] = self::get( $post_id )['settings']['webhooks']; } // JavaScript runs for every visitor: only those trusted with any markup write it if ( !current_user_can( 'unfiltered_html' ) ) { $config['settings']['js'] = self::get( $post_id )['settings']['js']; } $clean = self::sanitize( $config ); // Add-ons that are off or gone keep what was saved for them, and so do read-only ones (by their own word) $stored = json_decode( (string) get_post_meta( $post_id, self::META, true ), true ); $stored = (array) ( self::upgrade( is_array( $stored ) ? $stored : [] )['addons'] ?? [] ); foreach ( $stored as $slug => $values ) { if ( !isset( $clean['addons'][ $slug ] ) ) { $clean['addons'][ $slug ] = $values; } } foreach ( array_keys( $clean['addons'] ) as $slug ) { if ( '' !== Addons::readonly( (string) ( Addons::form_tabs()[ $slug ]['addon'] ?? $slug ) ) ) { if ( isset( $stored[ $slug ] ) ) { $clean['addons'][ $slug ] = $stored[ $slug ]; } else { unset( $clean['addons'][ $slug ] ); } } } $clean['addons'] = (object) $clean['addons']; update_post_meta( $post_id, self::META, wp_slash( wp_json_encode( $clean ) ) ); } /** * Run the upgrade steps a stored config hasn't had * * @param array $config Stored settings * * @return array */ public static function upgrade( array $config ) { $version = (int) ( $config['version'] ?? self::VERSION ); // [ version => callable ]: none yet $steps = []; foreach ( (array) apply_filters( 'remaily_config_upgrades', $steps ) as $step_version => $step ) { if ( $step_version > $version && is_callable( $step ) ) { $config = (array) $step( $config ); } } $config['version'] = self::VERSION; return $config; } /** * Keyed arrays merged, lists replaced * * @param array $defaults Defaults * @param array $config Values * * @return array */ public static function merge( array $defaults, array $config ) { foreach ( $config as $key => $value ) { if ( is_array( $value ) && isset( $defaults[ $key ] ) && is_array( $defaults[ $key ] ) && $defaults[ $key ] && !wp_is_numeric_array( $defaults[ $key ] ) ) { $defaults[ $key ] = self::merge( $defaults[ $key ], $value ); } else { $defaults[ $key ] = $value; } } return $defaults; } /** * Clean settings from the editor * * @param array $config Settings * * @return array */ public static function sanitize( array $config ) { $config = self::merge( self::defaults(), $config ); $clean = [ 'version' => self::VERSION ]; $clean['fields'] = []; foreach ( (array) $config['fields'] as $name => $field ) { $name = strtolower( (string) $name ); $type = FieldRegistry::get( (string) ( $field['type'] ?? '' ) ); if ( $type && Template::valid_name( $name ) && is_array( $field ) ) { $clean['fields'][ $name ] = $type->sanitize( $field ); } } $clean['groups'] = []; foreach ( (array) $config['groups'] as $name => $group ) { $name = strtolower( (string) $name ); if ( Template::valid_name( $name ) && is_array( $group ) ) { $clean['groups'][ $name ] = self::sanitize_group( $group ); } } $clean['mail'] = self::sanitize_mail( (array) $config['mail'] ); $clean['mail_2'] = [ 'active' => !empty( $config['mail_2']['active'] ) ] + self::sanitize_mail( (array) $config['mail_2'] ); $clean['messages'] = []; foreach ( (array) $config['messages'] as $key => $message ) { $message = sanitize_text_field( (string) $message ); // The defaults aren't stored, so a changed default reaches every form that kept it if ( '' !== $message && Messages::default( (string) $key ) !== $message ) { $clean['messages'][ sanitize_key( $key ) ] = $message; } } $settings = (array) $config['settings']; $clean['settings'] = [ 'style' => sanitize_key( (string) $settings['style'] ) ?: 'default', 'store_entries' => !empty( $settings['store_entries'] ), 'redirect' => esc_url_raw( (string) $settings['redirect'] ), 'hide_success' => !empty( $settings['hide_success'] ), 'class' => implode( ' ', array_map( 'sanitize_html_class', preg_split( '/\s+/', (string) $settings['class'], -1, PREG_SPLIT_NO_EMPTY ) ) ), 'autocomplete' => !empty( $settings['autocomplete'] ), 'demo' => !empty( $settings['demo'] ), 'skip_mail' => !empty( $settings['skip_mail'] ), 'subscribers_only' => !empty( $settings['subscribers_only'] ), 'captcha' => sanitize_key( (string) ( $settings['captcha'] ?? '' ) ), 'webhooks' => self::sanitize_urls( $settings['webhooks'] ), // Printed in a