core = $core; add_action( 'admin_init', [ $this, 'maybe_save' ] ); } // ─── Save ───────────────────────────────────────────────────────────────── public function maybe_save(): void { if ( empty( $_POST['patchwork_module'] ) ) return; $module = $this->core->module( sanitize_key( wp_unslash( $_POST['patchwork_module'] ) ) ); if ( ! $module ) return; check_admin_referer( 'patchwork_save_' . $module->id(), 'patchwork_nonce' ); if ( ! current_user_can( $module->settings_capability() ) ) { wp_die( esc_html__( 'You are not allowed to change these settings.', 'patchwork-order-split' ), 403 ); } foreach ( $module->settings_fields() as $field ) { $this->save_field( $field ); } do_action( 'patchwork_core_settings_saved', $module ); wp_safe_redirect( add_query_arg( 'updated', '1', $module->admin_url( 'settings' ) ) ); exit; } private function save_field( array $field ): void { $id = $field['id'] ?? ''; $type = $field['type'] ?? 'text'; if ( $id === '' ) return; if ( $type === 'custom' ) { if ( isset( $field['save'] ) && is_callable( $field['save'] ) ) call_user_func( $field['save'], $field ); return; } // Each value is sanitised for its type as it's read. Nonce verified in maybe_save(). // phpcs:disable WordPress.Security.NonceVerification.Missing $present = isset( $_POST[ $id ] ) && is_scalar( $_POST[ $id ] ); switch ( $type ) { case 'checkbox': $value = $present ? '1' : '0'; break; case 'lines': $value = $present ? sanitize_textarea_field( wp_unslash( $_POST[ $id ] ) ) : ''; break; case 'number': $raw = $present ? sanitize_text_field( wp_unslash( $_POST[ $id ] ) ) : ''; $value = (string) ( is_numeric( $raw ) ? 0 + $raw : ( $field['default'] ?? 0 ) ); break; case 'select': $raw = $present ? sanitize_text_field( wp_unslash( $_POST[ $id ] ) ) : ''; $options = array_map( 'strval', array_keys( (array) ( $field['options'] ?? [] ) ) ); $value = in_array( $raw, $options, true ) ? $raw : (string) ( $field['default'] ?? '' ); break; default: $value = $present ? sanitize_text_field( wp_unslash( $_POST[ $id ] ) ) : ''; } // phpcs:enable update_option( $id, $value ); } // ─── Render ─────────────────────────────────────────────────────────────── public static function render( Module $module ): void { $fields = $module->settings_fields(); $sections = $module->settings_sections(); if ( ! $fields ) { echo '
' . esc_html__( 'This module has no settings.', 'patchwork-order-split' ) . '
'; return; } // Group fields by section, keeping the declared section order $grouped = []; foreach ( $sections as $section_id => $title ) $grouped[ $section_id ] = []; foreach ( $fields as $field ) $grouped[ $field['section'] ?? '' ][] = $field; echo ''; } private static function render_field( array $field ): void { $id = $field['id']; $type = $field['type'] ?? 'text'; $value = get_option( $id, $field['default'] ?? '' ); $label = $field['label'] ?? ''; echo '' . esc_html( $field['description'] ) . '
'; } echo '