settings = $settings; $this->config = $config; $this->fonts = $fonts; $this->dir = $dir; $this->url = $url; $this->version = $version; } /** * Hook the route. */ public function register(): void { add_action( 'rest_api_init', array( $this, 'register_route' ) ); } /** * Register the route. */ public function register_route(): void { register_rest_route( self::NAMESPACE, self::ROUTE, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get' ), 'permission_callback' => array( $this, 'can_manage' ), ), array( 'methods' => WP_REST_Server::EDITABLE, 'callback' => array( $this, 'save' ), 'permission_callback' => array( $this, 'can_manage' ), ), ) ); } /** * Whether the current user may manage the plugin. */ public function can_manage(): bool { return current_user_can( Capabilities::MANAGE ); } /** * Current banner design settings. */ public function get(): WP_REST_Response { return new WP_REST_Response( $this->payload() ); } /** * Save the submitted fields. * * @param WP_REST_Request $request Request whose JSON body holds partial `layout`, `buttons` and `appearance` objects. * @return WP_REST_Response|WP_Error */ public function save( WP_REST_Request $request ) { $body = $request->get_json_params(); if ( ! is_array( $body ) ) { return new WP_Error( 'consentfold_invalid_body', __( 'Send the banner settings as a JSON object.', 'consentfold' ), array( 'status' => 400 ) ); } $locked = $this->locked_paths(); // The font and the family it shows are one setting: a lock on either covers both. if ( in_array( 'appearance.font', $locked, true ) || in_array( 'appearance.hosted_font', $locked, true ) ) { array_push( $locked, 'appearance.font', 'appearance.hosted_font' ); } $site = $this->settings->get_site(); $document = $site; $accepted = array(); // The appearance fields that passed the lock. foreach ( self::SECTIONS as $section ) { if ( ! is_array( $body[ $section ] ?? null ) ) { continue; } $kept = array_filter( $body[ $section ], static fn ( $field ): bool => ! in_array( $section . '.' . $field, $locked, true ) && ! in_array( $section, $locked, true ), ARRAY_FILTER_USE_KEY ); unset( $kept['font_inherit'] ); if ( 'appearance' === $section && is_string( $kept['font'] ?? null ) ) { $kept['font_inherit'] = 'theme' === $kept['font']; } if ( 'appearance' === $section ) { $accepted = $kept; } $document[ $section ] = array_merge( is_array( $site[ $section ] ?? null ) ? $site[ $section ] : array(), $kept ); } unset( $document['schema_version'] ); // The repository carries it over. $font_error = $this->hosted_font_error( (array) ( $document['appearance'] ?? array() ), $accepted ); if ( null !== $font_error ) { return new WP_Error( 'consentfold_invalid_settings', __( 'The banner settings are not valid.', 'consentfold' ), array( 'status' => 400, 'errors' => array( 'appearance.hosted_font' => $font_error ), ) ); } try { $this->settings->set( $document ); } catch ( InvalidSettingsException $e ) { return new WP_Error( 'consentfold_invalid_settings', __( 'The banner settings are not valid.', 'consentfold' ), array( 'status' => 400, 'errors' => $e->errors(), ) ); } // Only the family in use keeps its files. $appearance = (array) $this->settings->get()['appearance']; $this->fonts->prune( 'hosted' === ConfigBuilder::font( $appearance ) ? ConfigBuilder::hosted_family( $appearance ) : '' ); return new WP_REST_Response( $this->payload() ); } /** * Why the hosted font of a save cannot be used, or null when it can: the family must be one Google offers, in the * weights chosen, and, when it is the font in use, its files must be on this site. * * @param array $appearance Appearance section the save would store. * @param array $posted Appearance fields the request sent. */ private function hosted_font_error( array $appearance, array $posted ): ?string { $hosted = (array) ( $appearance['hosted_font'] ?? array() ); $family = ConfigBuilder::hosted_family( $appearance ); if ( array_key_exists( 'hosted_font', $posted ) && '' !== $family ) { try { $this->fonts->validate( $family, $hosted['weights'] ?? null ); } catch ( FontException $e ) { return $e->getMessage(); } } if ( 'hosted' === ( $posted['font'] ?? null ) && ( '' === $family || ! $this->fonts->is_installed( $family ) ) ) { return __( 'Choose a Google font and download it before you save.', 'consentfold' ); } return null; } /** * Effective settings of the three branches, the settings paths the network locked, and the preview inputs. * * @return array{layout: array, buttons: array, appearance: array, locked: list, preview: array{config: array, script: string, style: string, font_css: string}} */ private function payload(): array { $settings = $this->settings->get(); $appearance = (array) $settings['appearance']; // The font as the banner draws it (a legacy `font_inherit: false` reads as the system font); the old key stays out. $appearance['font'] = ConfigBuilder::font( $appearance ); $appearance = ConfigBuilder::animation( $appearance ) + $appearance; unset( $appearance['font_inherit'] ); $family = 'hosted' === $appearance['font'] ? ConfigBuilder::hosted_family( $appearance ) : ''; return array( 'layout' => ConfigBuilder::layout( (array) $settings['layout'] ), 'buttons' => (array) $settings['buttons'], 'appearance' => $appearance, 'locked' => $this->locked_paths(), 'preview' => array( 'config' => $this->config->build(), 'script' => $this->asset( 'js' ), 'style' => $this->asset( 'css' ), 'font_css' => '' === $family ? '' : $this->fonts->preview_css( $family ), ), ); } /** * URL of a built banner file, the one the site enqueues. * * @param string $extension File extension. */ private function asset( string $extension ): string { return add_query_arg( 'ver', rawurlencode( $this->version ), $this->url . BuiltAsset::path( $this->dir, 'assets/build/banner/' . Assets::HANDLE . '.' . $extension ) ); } /** * Locked paths inside the three branches, whole branches included. * * @return list */ private function locked_paths(): array { return array_values( array_filter( $this->settings->locked_keys(), static fn ( string $path ): bool => 1 === preg_match( '/^(?:layout|buttons|appearance)(?:\.|$)/', $path ) ) ); } }