namespace, '/public-agent', [ [ 'methods' => \WP_REST_Server::READABLE, 'callback' => [ $this, 'get_settings' ], 'permission_callback' => [ $this, 'permission_check' ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'save_settings' ], 'permission_callback' => [ $this, 'permission_check' ], ], ] ); } /** * Only a site administrator may read or change these settings. * * @return bool */ public function permission_check(): bool { return current_user_can( 'manage_options' ); } /** * Return the whole Public Agent payload. * * @param WP_REST_Request $request Request object. * @return \WP_HTTP_Response */ public function get_settings( WP_REST_Request $request ) { unset( $request ); return $this->response( $this->build_payload( new Settings() ) ); } /** * Clean and store the submitted Public Agent settings. * * Only keys declared in a schema are written, so the client cannot push * free-form data into the settings option. A panel missing from the request * keeps whatever it had, which is what lets a deactivated integration hold * its configuration. * * @param WP_REST_Request $request Request object. * @return \WP_HTTP_Response */ public function save_settings( WP_REST_Request $request ) { $body = $request->get_json_params(); if ( ! is_array( $body ) ) { $body = []; } $public_chat = []; $widget = $this->clean_widget( isset( $body['widget'] ) && is_array( $body['widget'] ) ? $body['widget'] : [] ); if ( $widget ) { $public_chat['widget'] = $widget; } $integrations = $this->clean_integrations( isset( $body['integrations'] ) && is_array( $body['integrations'] ) ? $body['integrations'] : [] ); if ( $integrations ) { $public_chat['integrations'] = $integrations; } if ( $public_chat ) { ( new Settings() )->merge_and_save( [ 'publicChat' => $public_chat ] ); } return $this->response( $this->build_payload( new Settings() ), __( 'Public agent settings saved', 'aisentic' ) ); } /** * Clean the global widget half of the request body. * * @param array $input Raw widget values. * @return array */ private function clean_widget( array $input ): array { $clean = []; foreach ( Panel_Registry::get_widget_schema() as $key => $field ) { if ( ! array_key_exists( $key, $input ) ) { continue; } $value = Field_Sanitizer::sanitize( $field, $input[ $key ] ); if ( null !== $value ) { $clean[ $key ] = $value; } } return $clean; } /** * Clean the per-integration half of the request body. * * @param array $input Raw values, keyed by panel id. * @return array */ private function clean_integrations( array $input ): array { $clean = []; foreach ( $input as $id => $values ) { $id = (string) $id; $panel = Panel_Registry::get_panel( $id ); if ( null === $panel || ! is_array( $values ) ) { continue; } $panel_clean = []; foreach ( $panel['fields'] as $key => $field ) { if ( ! array_key_exists( $key, $values ) ) { continue; } $value = Field_Sanitizer::sanitize( $field, $values[ $key ] ); if ( null !== $value ) { $panel_clean[ $key ] = $value; } } if ( $panel_clean ) { $clean[ $id ] = $panel_clean; } } return $clean; } /** * Build the payload the admin page renders from. * * @param Settings $settings Settings instance. * @return array */ private function build_payload( Settings $settings ): array { $agent = new Public_Agent_Settings( $settings ); $widget_schema = Panel_Registry::get_widget_schema(); $widget = []; foreach ( array_keys( $widget_schema ) as $key ) { $widget[ $key ] = $agent->widget( (string) $key ); } $panels = []; foreach ( Panel_Registry::get_active_panels() as $id => $panel ) { $values = []; foreach ( array_keys( $panel['fields'] ) as $key ) { $values[ $key ] = $agent->integration( (string) $id, (string) $key ); } $panels[] = [ 'id' => $panel['id'], 'label' => $panel['label'], 'icon' => $panel['icon'], 'slot' => $panel['slot'], 'docs' => $panel['docs'], 'fields' => self::json_safe_fields( $panel['fields'] ), 'values' => $values, ]; } return [ 'widget' => $widget, 'widgetSchema' => self::json_safe_fields( $widget_schema ), 'panels' => $panels, ]; } /** * Drop anything in a field that cannot survive json_encode. * * A panel author may put a closure or an object in a field by mistake; the * admin page only needs plain data. * * @param array $fields Normalized fields. * @return array */ private static function json_safe_fields( array $fields ): array { foreach ( $fields as $key => $field ) { if ( ! is_array( $field ) ) { unset( $fields[ $key ] ); continue; } foreach ( $field as $field_key => $field_value ) { if ( is_object( $field_value ) || is_resource( $field_value ) ) { unset( $fields[ $key ][ $field_key ] ); } } } return $fields; } }