$feature ) { $features[ $key ] = ! empty( $feature['on'] ); } $defaults = array( 'widget' => array( 'enabled' => true, 'position' => 'bottom-right', 'offset_x' => 20, 'offset_y' => 20, 'size' => 'medium', 'shape' => 'circle', 'icon' => 'birchtoad', 'accent' => '#2F57F0', 'header_title' => '', 'trigger_label' => '', 'open_on_load' => false, 'hide_on_mobile' => false, 'hide_for_logged_in' => false, 'shortcut' => true, // Off by default, in every edition, and switchable on the free build. // WordPress.org guideline 10 requires a credit display to default to not showing. 'credit' => false, ), 'features' => $features, 'statement' => array( 'page_id' => 0, ), 'languages' => array( 'mode' => 'site', 'default' => '', ), 'advanced' => array( 'exclude_ids' => '', 'post_types' => array(), 'uninstall_wipe' => false, ), ); /** * Filters the settings schema defaults. * * The paid build adds its own keys here rather than the core making room for them. * * @param array $defaults Defaults. */ return apply_filters( 'birchtoad_settings_schema', $defaults ); } /** * Every setting, defaults merged under whatever the site saved. * * @return array */ public static function all() { if ( null !== self::$cache ) { return self::$cache; } $saved = get_option( self::OPTION, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } $merged = self::defaults(); foreach ( $merged as $group => $fields ) { if ( isset( $saved[ $group ] ) && is_array( $saved[ $group ] ) ) { $merged[ $group ] = array_merge( $fields, $saved[ $group ] ); } } self::$cache = $merged; return self::$cache; } /** * One setting. * * @param string $group Group key. * @param string $key Field key. * @param mixed $fallback Returned when the field does not exist. * @return mixed */ public static function get( $group, $key, $fallback = null ) { $all = self::all(); if ( isset( $all[ $group ][ $key ] ) ) { return $all[ $group ][ $key ]; } return $fallback; } /** * Whether a feature exists in this edition and is switched on for the site. * * There is no license check here. A feature the edition cannot run is absent from the * registry, so an unknown key is simply not a feature. * * @param string $key Feature key. * @return bool */ public static function feature_on( $key ) { if ( null === BirchToad_Features::get( $key ) ) { return false; } return (bool) self::get( 'features', $key, false ); } /** * Saves a submitted settings group, sanitized. * * Starts from the merged array so a partial submission cannot drop keys the form did not * render. * * @param string $group Group key. * @param array $input Raw submitted values for that group. * @return bool Whether the option changed. */ public static function save_group( $group, array $input ) { $all = self::all(); if ( ! isset( $all[ $group ] ) ) { return false; } $all[ $group ] = self::sanitize_group( $group, $input, $all[ $group ] ); self::$cache = $all; return update_option( self::OPTION, $all ); } /** * Sanitizes one group against its current values. * * @param string $group Group key. * @param array $input Raw values. * @param array $current Current values, used as the type template and the fallback. * @return array */ private static function sanitize_group( $group, array $input, array $current ) { switch ( $group ) { case 'widget': return self::sanitize_widget( $input, $current ); case 'features': $out = array(); foreach ( BirchToad_Features::all() as $key => $feature ) { $out[ $key ] = ! empty( $input[ $key ] ); } return $out; case 'statement': $current['page_id'] = isset( $input['page_id'] ) ? absint( $input['page_id'] ) : 0; return $current; case 'languages': $modes = array( 'site', 'visitor', 'fixed' ); $mode = isset( $input['mode'] ) ? sanitize_key( $input['mode'] ) : 'site'; $current['mode'] = in_array( $mode, $modes, true ) ? $mode : 'site'; $current['default'] = isset( $input['default'] ) ? sanitize_text_field( wp_unslash( $input['default'] ) ) : ''; return $current; case 'advanced': $current['exclude_ids'] = isset( $input['exclude_ids'] ) ? self::sanitize_id_list( wp_unslash( $input['exclude_ids'] ) ) : ''; $current['post_types'] = isset( $input['post_types'] ) && is_array( $input['post_types'] ) ? array_values( array_map( 'sanitize_key', $input['post_types'] ) ) : array(); $current['uninstall_wipe'] = ! empty( $input['uninstall_wipe'] ); return $current; } /** * Filters the sanitized values of a settings group the core does not know. * * @param array $current Current values. * @param array $input Raw values. * @param string $group Group key. */ return apply_filters( 'birchtoad_sanitize_group', $current, $input, $group ); } /** * Sanitizes the widget group. * * @param array $input Raw values. * @param array $current Current values. * @return array */ private static function sanitize_widget( array $input, array $current ) { $positions = array( 'bottom-right', 'bottom-left', 'middle-right', 'middle-left', 'top-right', 'top-left' ); $sizes = array( 'small', 'medium', 'large' ); $shapes = array( 'circle', 'rounded', 'square' ); $icons = array( 'birchtoad', 'contrast', 'text', 'sliders', 'eye', 'wheelchair', 'none' ); $current['enabled'] = ! empty( $input['enabled'] ); $current['open_on_load'] = ! empty( $input['open_on_load'] ); $current['hide_on_mobile'] = ! empty( $input['hide_on_mobile'] ); $current['hide_for_logged_in'] = ! empty( $input['hide_for_logged_in'] ); $current['shortcut'] = ! empty( $input['shortcut'] ); $current['credit'] = ! empty( $input['credit'] ); $current['position'] = self::one_of( $input, 'position', $positions, $current['position'] ); $current['size'] = self::one_of( $input, 'size', $sizes, $current['size'] ); $current['shape'] = self::one_of( $input, 'shape', $shapes, $current['shape'] ); $current['icon'] = self::one_of( $input, 'icon', $icons, $current['icon'] ); $current['offset_x'] = isset( $input['offset_x'] ) ? min( 200, max( 0, absint( $input['offset_x'] ) ) ) : $current['offset_x']; $current['offset_y'] = isset( $input['offset_y'] ) ? min( 200, max( 0, absint( $input['offset_y'] ) ) ) : $current['offset_y']; if ( isset( $input['accent'] ) ) { $accent = sanitize_hex_color( wp_unslash( $input['accent'] ) ); $current['accent'] = $accent ? $accent : $current['accent']; } $current['header_title'] = isset( $input['header_title'] ) ? sanitize_text_field( wp_unslash( $input['header_title'] ) ) : $current['header_title']; $current['trigger_label'] = isset( $input['trigger_label'] ) ? sanitize_text_field( wp_unslash( $input['trigger_label'] ) ) : $current['trigger_label']; return $current; } /** * Returns a submitted value only when it is one of the allowed ones. * * @param array $input Raw values. * @param string $key Field key. * @param array $allowed Allowed values. * @param mixed $fallback Returned otherwise. * @return mixed */ private static function one_of( array $input, $key, array $allowed, $fallback ) { if ( ! isset( $input[ $key ] ) ) { return $fallback; } $value = sanitize_key( $input[ $key ] ); return in_array( $value, $allowed, true ) ? $value : $fallback; } /** * Normalizes a comma separated list of post IDs. * * @param string $raw Raw input. * @return string */ private static function sanitize_id_list( $raw ) { $ids = array_filter( array_map( 'absint', preg_split( '/[\s,]+/', (string) $raw ) ) ); return implode( ',', array_unique( $ids ) ); } /** * Clears the per-request cache. * * @return void */ public static function flush() { self::$cache = null; } }