options ) || ! method_exists( $wpdb, 'update' ) ) { return; } $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Sets autoload without rewriting the stored key. $wpdb->options, array( 'autoload' => 'no' ), array( 'option_name' => self::OPTION_NAME ) ); if ( function_exists( 'wp_cache_delete' ) ) { wp_cache_delete( 'alloptions', 'options' ); } } /** * Register with the same settings group as the tracking option. */ public static function register_setting() { register_setting( Causewise_Settings::OPTION_GROUP, self::OPTION_NAME, array( 'type' => 'array', 'sanitize_callback' => array( __CLASS__, 'sanitize' ), 'default' => self::defaults(), 'show_in_rest' => false, ) ); } /** * Default stored values. The key is empty until an administrator saves one. * * @return array{api_key: string, site_id: string} */ public static function defaults() { return array( 'api_key' => '', 'site_id' => '', ); } /** * Saved credentials. Call this only from server-side dashboard code. * * @return array{api_key: string, site_id: string} */ public static function get() { $stored = get_option( self::OPTION_NAME, array() ); if ( ! is_array( $stored ) ) { $stored = array(); } $settings = array_merge( self::defaults(), $stored ); $api_key = self::normalize_api_key( $settings['api_key'] ); $site_id = self::normalize_site_id( $settings['site_id'] ); return array( 'api_key' => self::is_valid_api_key( $api_key ) ? $api_key : '', 'site_id' => self::is_valid_site_id( $site_id ) ? $site_id : '', ); } /** * Whether a management key is stored. * * @return bool */ public static function has_api_key() { $credentials = self::get(); return '' !== $credentials['api_key']; } /** * Remove whitespace from a pasted key. * * @param mixed $key Raw value. * @return string */ public static function normalize_api_key( $key ) { $key = preg_replace( '/\s+/', '', (string) $key ); if ( ! is_string( $key ) ) { return ''; } return $key; } /** * Management keys are cw_api_ plus a base64url secret. * * @param mixed $key Candidate. * @return bool */ public static function is_valid_api_key( $key ) { return 1 === preg_match( '/^cw_api_[A-Za-z0-9_-]{10,200}$/', self::normalize_api_key( $key ) ); } /** * Trim a site ID. * * @param mixed $site_id Raw value. * @return string */ public static function normalize_site_id( $site_id ) { return trim( (string) $site_id ); } /** * Site IDs are opaque URL-safe identifiers, not tracking or API keys. * * @param mixed $site_id Candidate. * @return bool */ public static function is_valid_site_id( $site_id ) { $site_id = self::normalize_site_id( $site_id ); if ( '' === $site_id ) { return false; } if ( 0 === strpos( $site_id, 'cw_' ) ) { return false; } return 1 === preg_match( '/^[A-Za-z0-9_-]{8,64}$/', $site_id ); } /** * Sanitize the dashboard credential form. * * An empty key field keeps the stored key. The clear checkbox removes it. * Invalid values are not written. * * @param mixed $input Posted value. * @return array{api_key: string, site_id: string} */ public static function sanitize( $input ) { $previous = self::get(); if ( ! current_user_can( Causewise_Settings::CAPABILITY ) ) { return $previous; } if ( ! is_array( $input ) ) { return $previous; } $api_key = $previous['api_key']; if ( ! empty( $input['clear_api_key'] ) ) { $api_key = ''; } else { $posted = self::normalize_api_key( isset( $input['api_key'] ) ? wp_unslash( $input['api_key'] ) : '' ); if ( '' !== $posted ) { if ( self::is_valid_api_key( $posted ) ) { $api_key = $posted; } else { add_settings_error( self::OPTION_NAME, 'causewise_invalid_api_key', __( 'Enter the management API key from CauseWise. The public tracking key cannot show analytics in WordPress.', 'causewise-analytics' ), 'error' ); } } } $site_id = $previous['site_id']; if ( array_key_exists( 'site_id', $input ) ) { $posted_site = self::normalize_site_id( wp_unslash( $input['site_id'] ) ); if ( '' === $posted_site ) { $site_id = ''; } elseif ( self::is_valid_site_id( $posted_site ) ) { $site_id = $posted_site; } else { add_settings_error( self::OPTION_NAME, 'causewise_invalid_site_id', __( 'Enter the site ID from CauseWise, or leave it blank.', 'causewise-analytics' ), 'error' ); } } return array( 'api_key' => $api_key, 'site_id' => $site_id, ); } }